Python学习笔记: 闭包
闭包的基本定义
在计算机科学中,闭包(英语: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
分析:
- 这个例子中,函数line与变量a,b构成闭包。在创建闭包的时候,通过line的参数 k, b 说明了这两个变量的取值,这样,就确定了函数的最终形式(y = x + 1和y = 4x + 1)。只需要变换参数a,b,就可以获得不同的直线表达函数。由此,可以看出,闭包具有跟函数类似的提高代码可复用性的作用。
- 如果没有闭包,每次新创建直线函数的时候需要同时指定 k,b,x。这样,就需要更多的参数传递,也减少了代码的可移植性。
注意点:
- 由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,会消耗内存。
- 但是相较于类而言,其已经大大降低了内存占用
为了进一步的对 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学习笔记: 闭包的更多相关文章
- Python学习笔记(九)
Python学习笔记(九): 装饰器(函数) 内置函数 1. 装饰器 1. 作用域 2. 高阶函数 3. 闭包 如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就 ...
- Python学习笔记:装饰器
Python 装饰器的基本概念和应用 代码编写要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即: 封闭:已 ...
- Python学习笔记之生成器、迭代器和装饰器
这篇文章主要介绍 Python 中几个常用的高级特性,用好这几个特性可以让自己的代码更加 Pythonnic 哦 1.生成器 什么是生成器呢?简单来说,在 Python 中一边循环一边计算的机制称为 ...
- Python学习笔记(四)函数式编程
高阶函数(Higher-order function) Input: 1 abs Output: 1 <function abs> Input: 1 abs(-10) Output: 1 ...
- python学习笔记整理——字典
python学习笔记整理 数据结构--字典 无序的 {键:值} 对集合 用于查询的方法 len(d) Return the number of items in the dictionary d. 返 ...
- VS2013中Python学习笔记[Django Web的第一个网页]
前言 前面我简单介绍了Python的Hello World.看到有人问我搞搞Python的Web,一时兴起,就来试试看. 第一篇 VS2013中Python学习笔记[环境搭建] 简单介绍Python环 ...
- python学习笔记之module && package
个人总结: import module,module就是文件名,导入那个python文件 import package,package就是一个文件夹,导入的文件夹下有一个__init__.py的文件, ...
- python学习笔记(六)文件夹遍历,异常处理
python学习笔记(六) 文件夹遍历 1.递归遍历 import os allfile = [] def dirList(path): filelist = os.listdir(path) for ...
- python学习笔记--Django入门四 管理站点--二
接上一节 python学习笔记--Django入门四 管理站点 设置字段可选 编辑Book模块在email字段上加上blank=True,指定email字段为可选,代码如下: class Autho ...
- python学习笔记--Django入门0 安装dangjo
经过这几天的折腾,经历了Django的各种报错,翻译的内容虽然不错,但是与实际的版本有差别,会出现各种奇葩的错误.现在终于找到了解决方法:查看英文原版内容:http://djangobook.com/ ...
随机推荐
- Oracle使用SQL语句修改字段类型
Oracle使用SQL语句修改字段类型 1.如果表中没有数据 Sql代码 1 2 3 alter table 表名 modify (字段名1 类型,字段名2 类型,字段名3 类型.....) alt ...
- Auto yes to the License Agreement on sudo apt-get -y install oracle-java7-installer
参考一 参考二 我自己的做法是: && add-apt-repository ppa:webupd8team/java \ && apt-get update \ &a ...
- csu 1554: SG Value 思维题
http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1554 这题在比赛的时候居然没想出来,然后发现居然是做过的题目的变种!!!! 先不考虑插入操作, ...
- .net程序员业余Android开发赚点外快(介绍一下自己的经验)
记得是11年10月份开始研究android的,当时还不会java,听说android比较火,自己也买了个垃圾android机,平时工作也不是特别忙,于是我就突发奇想,想试试做一下android应用可不 ...
- (办公)ssm发送邮件
1.添加jar包 <!-- Javamail API --> <dependency> <groupId>javax.mail</groupId> &l ...
- Redis set(集合)
Redis 的 Set 是 String 类型的无序集合,元素不允许重复. Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1). 集合中最大的元素数为 232 - 1 ( ...
- [拾零]C语言的数组指针
为了强化记忆,从而写笔记保留. 数组指针,顾名思义,是在说一个指针,这个指针是指向数组的. 区别于指针数组 int* p[5] = NULL; //指针数组 基类型 int* int (*p)[5] ...
- iOS - KVO 简单应用
KVO(键值监听)全称 Key Value Observing.使用KVO可以实现视图组件和数据模型的分离,视图作为监听器,当模型的属性值发生变化后,监听器可以做相应的处理.KVO的方法由NSKeyV ...
- Elasticsearch-基本操作1
Elasticsearch版本:6.0 一.文档 一个文档不仅包含数据,也包含元数据,三个必须的元数据如下 _index:具有共同特性分到一起的文档集合,标示了文档的存放位置: 名字小写,不以下划线开 ...
- 数组Array和字符串String的indexOf方法,以及ES7(ES2016)中新增的Array.prototype.includes方法
前言 我们在判断某一个字符是否存在于一个字符串中或者某一个值是否存在于一个数组中时,ES7之前我们需要使用indexOf,ES7引入了新的方法includes 语法 数组:Array.inexOf(s ...