闭包的基本定义

在计算机科学中,闭包(英语: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. JS代码运行延迟

    还是上篇文章的项目. 现在是屏幕上需要显示九张图表,刚好用一张3X3的表格来显示.但是负责这块内容的同事始终没法让九张图表同时显示,有些图表的位置空了出来. 大家百思不得其解,最后只得求助技术经理. ...

  2. @Column 注解详情

    @Column标记表示所持久化属性所映射表中的字段,该注释的属性定义如下: @Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface ...

  3. 线程池ThreadPoolExecutor的学习

    我们知道,ExecutorService是一个抽象出线程池的一个接口,然后我们在使用线程池的时候,用的是Executors工具类中的一系列newCachedThreadPool() 等类似的方法,这些 ...

  4. Magic Maze dfs + dp

    http://swjtuoj.cn/problem/2387/ 设dp[cur]表示以cur这个节点为起点的时候,能走的最大贡献. 题目保证没环,也就是没回路. 感觉和树dp差不多了. #includ ...

  5. dao层写展示自己需要注意的问题

    写dao层时一定要把 News news=new News(); 写在while循环内,是每循环一次,new出一个对象

  6. Bootstrap设置按钮禁用

    在Bootstrap中,按钮可以使用button标签或者a标签.设置按钮禁用可以通过两种方式,一种是通用CSS样式,一种是用过JS脚本动态设置,下面举例说明! <!DOCTYPE html> ...

  7. 在jquery事件中修改Angular的model

    HTML代码如下 <!DOCTYPE html> <html ng-app="qm"> <body> <input type=" ...

  8. Protocol Buffer学习教程之开篇概述(一)

    1. Protocol Buffer是什么 Protocol Buffer是google旗下的产品,用于序列化与反序列化数据结构,但是比xml更小.更快.更简单,而且能跨语言.跨平台.你可以把你的数据 ...

  9. Python+selenium之unittest单元测试(3)关于测试用例执行的顺序

    一.测试用例执行的顺序 用例的执行顺序涉及多个层级,在多个测试目录的情况下,先执行哪个目录?在多个测试文件的情况下,先执行哪个文件?在多个测试类的情况下,先执行哪个测试类?,在多个测试方法(用例)的情 ...

  10. ssh连接github连不上

    连接github报端口22连接不上: 输入命令展示出ssh_config内容后: vim /etc/ssh/ssh_config 或者使用open /etc/ssh/ssh_config命令在文本编辑 ...