闭包的基本定义

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。详见 维基百科

Python 中的闭包

首先从例子引入 Python3 中的闭包

 #!/usr/bin/env python3
#-*- coding:utf-8 -*-
#File Name:01_closure.py
#Created Time:2019-01-09 09:32:20 def line(k, b):
# 在函数内部再定义一个函数,并且这个函数用到了外部函数的变量,
# 这个内部函数的用到的相关变量即被称为闭包
# 本例中,k,b 和 x 即为闭包
def foo(x):
return k*x + b
return foo # 返回内部函数的引用 if __name__ == "__main__": print("***** y = x + 1 *****")
a = line(1,1)
print("x = 1 --> y = %d" % a(1))
print("x = 2 --> y = %d" % a(2))
print("x = 3 --> y = %d" % a(3)) print("***** y = 4 * x + 1 *****")
b = line(4,1)
print("x = 1 --> y = %d" % b(1))
print("x = 2 --> y = %d" % b(2))
print("x = 3 --> y = %d" % b(3))

例程

运行结果:

***** y = x + 1 *****
x = 1 --> y = 2
x = 2 --> y = 3
x = 3 --> y = 4
***** y = 4 * x + 1 *****
x = 1 --> y = 5
x = 2 --> y = 9
x = 3 --> y = 13

分析:

  1. 这个例子中,函数line与变量a,b构成闭包。在创建闭包的时候,通过line的参数 k, b 说明了这两个变量的取值,这样,就确定了函数的最终形式(y = x + 1和y = 4x + 1)。只需要变换参数a,b,就可以获得不同的直线表达函数。由此,可以看出,闭包具有跟函数类似的提高代码可复用性的作用。
  2. 如果没有闭包,每次新创建直线函数的时候需要同时指定 k,b,x。这样,就需要更多的参数传递,也减少了代码的可移植性。

注意点:

  1. 由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,会消耗内存。
  2. 但是相较于类而言,其已经大大降低了内存占用

为了进一步的对 Python3 中的闭包的调用流程进行说明,再看以下例程:

 #!/usr/bin/env python3
#-*- coding:utf-8 -*-
#File Name: 02_closure_test.py
#Created Time:2019-01-09 09:32:20 import sys def line(k, b):
# 在函数内部再定义一个函数,并且这个函数用到了外部函数的变量,
# 这个内部函数的用到的相关变量即被称为闭包
# 本例中,k,b 和 x 即为闭包
def foo(x):
return k*x + b
print("foo 函数的 id 为:\t\t%d" % id(foo))
return foo if __name__ == "__main__": print("***** y = x + 1 *****")
print("line 的 id 为: \t\t\t%d" % id(line))
a = line(1,1) # 会打印 foo 的 id
print("a = line(1,1),a 的 id 为: \t%d" % id(a))
print("x = 1 --> y = %d" % a(1))
print("***** y = 3*x + 2 *****")
print("line 的 id 为: \t\t\t%d" % id(line))
b = line(3,2) # 会打印 foo 的 id
print("b = line(3,2),b 的 id 为: \t%d" % id(b))
print("x = 1 --> y = %d" % b(1))

闭包例程

运行结果:

***** y = x + 1 *****
line 的 id 为: 2452122878432
foo 函数的 id 为: 2452124629872
a = line(1,1),a 的 id 为: 2452124629872
x = 1 --> y = 2
***** y = 3*x + 2 *****
line 的 id 为: 2452122878432
foo 函数的 id 为: 2452124630008
b = line(3,2),b 的 id 为: 2452124630008
x = 1 --> y = 5

从结果中可看出,line 函数的 id 没有发生改变,而不同闭包的 id 则各不相同。说明闭包在运行时可以有多个不同的实例

运行过程分析

Python 解释器运行到 1 处时,会将 line 函数加载到内存中。

运行到 2 处时,会调用内存中的 line 函数,创建(图中的 3 )相应的内存空间 8,通过 14 行中的 retrun 语句,使得 a 指向了内存 8 中的 foo 函数。 第 26 行执行的过程类似,只不过又开辟了一块新的内存空间。

由于 line 函数中的 foo 被外部程序所引用,因此 8,9 内存空间并不会随着 line 函数运行的结束而被释放。从而使用相应的 foo 函数可以被重复调用。

Python3 闭包修改外部变量的值

可以借助 noloacl 关键字,也可借助列表,下面的例程演示 nolocal 关键字实现的方法:

 #!/usr/bin/env python3
#-*- coding:utf-8 -*-
#File Name: 03_closure_modify_local_var.py
#Created Time:2019-01-09 09:32:20 import sys def line(k, b):
# 在函数内部再定义一个函数,并且这个函数用到了外部函数的变量,
# 这个内部函数的用到的相关变量即被称为闭包
# 本例中,k,b 和 x 即为闭包
def foo(x):
nonlocal k
k += 1
print("***** k = %d" % k)
return k*x + b
return foo if __name__ == "__main__": print("***** y = x + 1 *****")
a = line(1,1)
print("x = 1 --> y = %d" % a(1))
print("x = 2 --> y = %d" % a(2))
print("x = 3 --> y = %d" % a(3)) print("***** y = 4*x + 1 *****")
b = line(4,1)
print("x = 1 --> y = %d" % b(1))
print("x = 2 --> y = %d" % b(2))
print("x = 3 --> y = %d" % b(3))

修改外部变量

运行结果:

***** y = x + 1 *****
***** k = 2
x = 1 --> y = 3
***** k = 3
x = 2 --> y = 7
***** k = 4
x = 3 --> y = 13
***** y = 4*x + 1 *****
***** k = 5
x = 1 --> y = 6
***** k = 6
x = 2 --> y = 13
***** k = 7
x = 3 --> y = 22

程序中如果没有用 nolocal 关键字,则程序会报错,因为 Python 解释器不能断定此时的 k 到底是函数 line 内的变量,还是函数 foo 类的局部变量。

Python学习笔记: 闭包的更多相关文章

  1. Python学习笔记(九)

    Python学习笔记(九): 装饰器(函数) 内置函数 1. 装饰器 1. 作用域 2. 高阶函数 3. 闭包 如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就 ...

  2. Python学习笔记:装饰器

    Python 装饰器的基本概念和应用 代码编写要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即: 封闭:已 ...

  3. Python学习笔记之生成器、迭代器和装饰器

    这篇文章主要介绍 Python 中几个常用的高级特性,用好这几个特性可以让自己的代码更加 Pythonnic 哦 1.生成器 什么是生成器呢?简单来说,在 Python 中一边循环一边计算的机制称为 ...

  4. Python学习笔记(四)函数式编程

    高阶函数(Higher-order function) Input: 1 abs Output: 1 <function abs> Input: 1 abs(-10) Output: 1 ...

  5. python学习笔记整理——字典

    python学习笔记整理 数据结构--字典 无序的 {键:值} 对集合 用于查询的方法 len(d) Return the number of items in the dictionary d. 返 ...

  6. VS2013中Python学习笔记[Django Web的第一个网页]

    前言 前面我简单介绍了Python的Hello World.看到有人问我搞搞Python的Web,一时兴起,就来试试看. 第一篇 VS2013中Python学习笔记[环境搭建] 简单介绍Python环 ...

  7. python学习笔记之module && package

    个人总结: import module,module就是文件名,导入那个python文件 import package,package就是一个文件夹,导入的文件夹下有一个__init__.py的文件, ...

  8. python学习笔记(六)文件夹遍历,异常处理

    python学习笔记(六) 文件夹遍历 1.递归遍历 import os allfile = [] def dirList(path): filelist = os.listdir(path) for ...

  9. python学习笔记--Django入门四 管理站点--二

    接上一节  python学习笔记--Django入门四 管理站点 设置字段可选 编辑Book模块在email字段上加上blank=True,指定email字段为可选,代码如下: class Autho ...

  10. python学习笔记--Django入门0 安装dangjo

    经过这几天的折腾,经历了Django的各种报错,翻译的内容虽然不错,但是与实际的版本有差别,会出现各种奇葩的错误.现在终于找到了解决方法:查看英文原版内容:http://djangobook.com/ ...

随机推荐

  1. CodeForces - 508B-Anton and currency you all know

    Berland, 2016. The exchange rate of currency you all know against the burle has increased so much th ...

  2. CoreRT

    使用CoreRT将.NET Core发布为Native应用程序 在上一篇文章<使用.NET Core快速开发一个较正规的命令行应用程序>中我们看到了使用自包含方式发布的.NET Core应 ...

  3. linux 环境下备份oracle 数据库

    登陆linux后,进入oracle的安装目录下,找到bin那个目录,进入bin目录ls -l 看这些命令的所有者: su - oracle这时会进入这个用户的主目录/home/oracle,此时,可以 ...

  4. 字典(dict),增删改查,嵌套

    一丶字典 dict 用{}来表示  键值对数据  {key:value}  唯一性 键 都必须是可哈希的 不可变的数据类型就可以当做字典中的键 值 没有任何限制 二丶字典的增删改查 1.增 dic[k ...

  5. 【js类库Raphaël】使用raphael.js根据点坐标绘制平滑曲线

     一.可供参考的文档资料. raphaeljs官网:http://raphaeljs.com/ w3c关于path的介绍:http://www.w3.org/TR/2003/REC-SVG11-200 ...

  6. Elasticsearch-基本操作1

    Elasticsearch版本:6.0 一.文档 一个文档不仅包含数据,也包含元数据,三个必须的元数据如下 _index:具有共同特性分到一起的文档集合,标示了文档的存放位置: 名字小写,不以下划线开 ...

  7. dstat工具使用介绍

    一.dstat工具多功能系统资源统计生成工具.获取信息类似top.free.iostat.vmstat等多个工具的合集,所以也称为vmstat.iostat.ifstat等工具替代品,其结果可以存储成 ...

  8. python基础教程总结15——6 CGI远程编辑

    功能: 将文档作为普通网页显示: 在web表单的文本域内显示文档: 保存表单中的文本: 使用密码保护文档: 容易拓展,支持处理多余一个文档的情况 1.CGI CGI(Comment Gateway I ...

  9. 【UML】协作图Collaboration diagram(交互图)(转)

    http://blog.csdn.net/sds15732622190/article/details/49402269 前言         学完UML时序图,就要看一下UML协作图,因为两张图是相 ...

  10. 使用ServiceController组件控制计算机服务

    实现效果: 知识运用: ServiceController组件的MachineName属性 //获取或设置服务所驻留的计算机名称 public string MachineName{get;set;} ...