文章目录

  • 一、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()运行时的环境。

python blit blitme 函数 python blend_Python

二、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")