python的闭包简介

LEGB法则

python变量的作用域遵从LEGB法则,即:

  • Local(L):定义在方法或类内部的变量,如 def 或 lambda 函数内部
  • Enclosed(E):闭包内部变量(仅限闭包)
  • Global(G):全局变量
  • Built-in(B):python内置的关键字

对任意一个变量,python会按照上述顺序依次查找

这意味着python中有且仅有这四个作用域,最小作用域范围就是方法,而没有其他语言中的块作用域,例如下述代码:

def func():
    if True:
        v = 'bingo'
    print(v)

这段代码如果在java中执行就会无法编译,因为v的作用域局限在if的代码块中。而在python中就不会,因为python的最小作用域是func。同样,对于for、while这些代码块也是没有作用域的

闭包

英文名为 enclosing function,我更喜欢称之为“嵌套函数”,因为其本身就是“函数内部定义的函数”

def outlier():
    def inner():    # 这个函数就是闭包
        ...
    ...
# 你可以在outlier函数内部直接调用inner函数,也可以直接将inner函数作为返回值

这样做的目的是什么呢?我觉得就是创建出一个作用域。如果不看python内置变量的话,在没有闭包的情况下,python仅仅只有两个作用域,对于一个变量,要么在func内部找,要么去全局里面找。这样做的话,对于func内部变量还好说,因为变量已经被func根据不同的功能隔离开了,但对于全局变量的管理就比较混乱了,各种不同功能的全局变量一股脑都在全局环境中。但引入闭包就不同了,它又将func视作一个全局变量环境(仅针对闭包来说),一层一层嵌套,就能更好地管理“全局变量”(并非真的全局变量,仅仅对闭包而言)

这就好比是:在以前,村民有什么问题都去找酋长。现在这种责任关系通过进一步的细分,村民有问题找村长,村长解决不了找县长,县长解决不了找市长。。。

一个综合一点的题目

为什么输出结果是这样?

arr = []
for i in range(3):
    def inner(num):
        return num * i
    arr.append(inner)

for func in arr:
    print(func(2))

# 输出结果:4  4  4

分析:

从作用域来说,由于for是没有作用域的,故代码其实等价于:

arr = []

i = 0   # 由于for没有定义域,故for内部定义的变量i等价于在外部定义

def inner(num):     # 由于for没有定义域,故for内部定义的函数inner等价于在外部定义
    return num * i

for counter in range(3):    # for会先给外部变量i赋值,再将函数添加到arr中
    i = counter
    arr.append(inner)

for func in arr:
    print(func(2))

看到没,对于函数inner来说,i就是全局变量,而对于main来说,它是局部变量。这个作用域太大了,而我们仅仅是希望将其限制在for和inner之间。所以,一个解决方法是使用闭包:

arr = []
for i in range(3):
    def outlier(idx):    # 使用闭包增加一个作用域
        def inner(num):
            return num * idx
        return inner
    arr.append(outlier(i))    # i这个变量将被储存在该作用域内

for func in arr:
    print(func(2))

这又有点像docker这种容器化技术了,原本我使用物理机完成某件事,但所有资源放在物理机上不好管理,我就可以创造出很多的容器,每个容器依照功能不同可以传递不同的参数,该参数在整个容器中生效。

Leave a Comment