文章目录
- 一、Something First
- 对象和变量
- LEGB 规则
- 二、Precondition
- First-Class Functions 头等函数
- 嵌套函数
- 三、Closure
- 什么是闭包
- 显式查看闭包
- 应用场景
- 四、参考资料
一、Something First
我们先来讨论下待会会用到的几个概念。
对象和变量
Python 中,一切皆对象。
对象的本质就是:一个内存块(堆),拥有特定的值,支持特定类型的相关操作。
在Python 中,变量也成为:对象的引用。因为,变量存储的就是对象的地址。
变量通过地址引用了“对象”。
变量位于:栈内存。
对象位于:堆内存。
更具体的点这里~
LEGB 规则
Python 在查找“变量”时,是按照LEGB 规则查找的:
Local → Enclosed → Global → Built in
Local 指的就是函数或者类的方法内部。
Enclosed 指的是嵌套函数。
Global 指的是模块中的全局变量。
Built in 指的是Python 为自己保留的特殊名称。
如果某个 name 映射在局部(Local)命名空间中没有找到,接下来就会在嵌套函数的作用域(Enclosed)进行搜索,如果嵌套函数的作用域也没有找到,Python 就会到全局(Global)命名空间中进行查找,最后会在内建(Built-in)命名空间搜索(如果一个名称在所有命名空间中都没有找到,就会产生一个(NameError)。
我们来看下面这个例子:
a = 1
def outer():
a = 2
def inner():
a = 3
print('最里面的:', a)
print('中间的: ', a)
return inner
if __name__ == '__main__':
print('最外面的:', a)
t = outer() # outer()返回的是inner()的引用
t()
运行结果:
最外面的: 1
中间的: 2
最里面的: 3
可以发现,在inner()
里定义的 a,对outer()
里的 a 没有产生影响。在outer()
里定义的 a,对函数外的 a 也没有产生影响。即它们都有各自的作用域(Scope)。
实际上,在这段代码运行的过程中,PVM 首先遇到a = 1
,a 存储在栈中,1 存储在堆中,并 a 指向 1。接着遇到def
块,将其编译并放到内存中,但并没有执行。
随后执行if
块里的语句。当执行outer()
时,PVM 会在栈中创建一个栈帧(Stack Frame),用于保存函数运行时的环境。
最后执行t()
,这一步相当于执行inner()
,这时 PVM 又会创建一个栈帧,用于保存inner()
运行时的环境。
二、Precondition
我们先来看下实现闭包的几个前提条件:
First-Class Functions 头等函数
这个技术可以让你的函数就像变量一样来使用。也就是说,你的函数可以像变量一样被创建,修改,并当成变量一样传递,返回或是在函数中嵌套函数。
为了解释什么是头等函数,我们先来讨论下什么是类型。
类型,定义了一个取值的集合,以及可作用的操作的集合。如 C 语言的 int 类型有一个上下界,可进行加减乘除等操作。变量可能有 int,float,string。。等类型,函数、对象等也有类型。
进一步,我们把类型分为三等:
1)First Class。该类型的值可以作为函数的参数和返回值,也可以赋给变量。
2)Second Class。该类型的值可以作为函数的参数,但不能从函数返回,也不能赋给变量。
3)Third Class。该类型的值作为函数参数也不行
所以头等函数即函数类型为First Class。
嵌套函数
一个典型的嵌套函数像这样:
def outer():
def inner():
pass
return inner
三、Closure
什么是闭包
在 Python 中的定义:如果在一个内层函数里,对外层函数作用域(不是函数外的全局作用域)的变量进行引用,那么这个内层函数就被认为是闭包(Closure)。
从狭义上讲,闭包就是引用了外层函数的变量的内层函数。
从广义上讲,闭包 = 函数块 + 运行环境(如外层函数被引用的变量就是环境的一部分)。
显式查看闭包
def outer(a):
b = 1
def inner():
print(a + b)
return inner
if __name__ == '__main__':
o = outer(2)
print(o.__closure__)
运行结果:
(<cell at 0x000001D1452F2468: int object at 0x00000000690F60C0>,
<cell at 0x000001D1453346A8: int object at 0x00000000690F60A0>)
不难看到,__closure__
返回了闭包中引用的变量元组。其中,元组中的两个int object
,即引用自外层函数outer()
的变量 a、b。
应用场景
1、保护且能访问函数内变量。
def outer(a):
b = 1
def inner():
print(a + b)
return inner
if __name__ == '__main__':
o = outer(2)
o()
输出结果:
3
2、当闭包执行完后,仍然能够保持住当前的运行环境。
origin = [0, 0] # 坐标系统原点
def create(pos=origin):
def player(direction, step):
new_x = pos[0] + direction[0]*step
new_y = pos[1] + direction[1]*step
pos[0] = new_x # 不能写成 pos = [new_x, new_y]
pos[1] = new_y # 调用 pos,而不是创建 pos
return pos
return player
player = create() # 创建棋子player,起点为原点
print(player([1,0],10)) # 向x轴正方向移动10步
print(player([0,1],20)) # 向y轴正方向移动20步
print(player([-1,0],10)) # 向x轴负方向移动10步
运行结果:
[10, 0]
[10, 20]
[0, 20]
3、闭包可以根据外部作用域的局部变量来得到不同的结果。这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。
比如有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行。
def make_filter(key_word):
def the_filter(file_name):
file = open(file_name)
lines = file.readlines()
file.close()
filter_doc = [i for i in lines if key_word in i]
return filter_doc
return the_filter
如果我们需要取得文件"result.txt"
中含有 pass 关键字的行,则可以这样:
filter = make_filter("pass")
filter_result = filter("result.txt")