Python中的闭包
- Python对闭包的官方说法:闭包表示一个内部函数里对外部作用域(但不是全局作用域)的变量进行引用,就认为内部函数是闭包。
- 闭包可以形象的理解为一个封闭的包裹,这个包裹就是一个函数,且这个函数有一个特点,它将外部作用域中的变量也包裹进去了。
- 闭包意味着,如果调用一个函数A,这个函数A返回一个函数B,那么称这个返回的函数B为闭包。
1. 下面是对闭包的演示代码
def func(name): #func为外部函数A
def func1(age): #func1为内部函数B,且内部函数将外部作用域中的变量name也包裹进去,形成闭包
print('name:',name,'age:',age) #此处调用的name为外部作用域(func函数的作用域)中的变量
return func1 #返回一个func1的函数对象,也就是一个闭包,其中带有变量name
my_age = func('BigBoss') #用变量my_age来接收func返回的函数对象
my_age(19) #相当于调用func1(19)
my_age = func('BigBigBoss')
my_age(20)
- 在上述代码中,当调用函数func(‘BigBoss’)时就产生一个闭包func1(),并用my_age来接收,注意此时func函数的传参name就是调用时func函数的实参。
- 在该示例中,闭包func1()包裹了外部变量name,这表示当函数func()的生命周期结束后,变量name依然会存在,应为它被闭包引用了,所以不会被系统回收,故才可以被闭包函数func1()和函数my_age()使用。
- my_age接受这个闭包后就相当于给闭包取了个叫做
my_age
的名字,故my_age(age) == func1(age),注意此时func1函数的传参age就是my_age函数的实参。
运算结果
name: BigBoss age: 19
name: BigBigBoss age: 20
2. 下面演示闭包的实例——使用闭包记录函数的调用次数
def hello_counter(num = 0): #默认参数num的初始化为0
count = num #count作为相对于函数counter()的外部作用域中的参数
def counter(): #闭包
nonlocal count #告诉解释器这个count变量是在外部定义的
count += 1
print('次数:',count)
return counter #返回闭包
hello = hello_counter() #使用变量hello接收函数对象counter
hello()
hello() #调用两次函数hello()
- 在上述代码中,外部函数将num赋值给count,作为计数使用。
- 首先使用变量hello来接受函数hello_counter()返回的闭包counter(),故此时hello() == counter(),故每调用一次hello()就相当于调用一次counter(),而闭包counter()能够让外部变量count自增,因此可以达到计算函数调用次数的目的。
- 注:此处用到了python关键字nonlocal,该关键字的作用是告诉解释器这个count变量实在函数counter()的外部定义了,有了这个关键字,python就会从外层函数寻找变量count。
运算结果
次数: 1
次数: 2
3. 下面演示闭包的应用——装饰器
- 在python语言中,可以使用装饰器给不同的函数或类插入相同的功能,且不影响原有函数和类的功能,还能添加新的功能。
- python中使用@符号来实现装饰器,在定义装饰器装饰函数或类时,使用“
@装饰器名称
”的形式将符号“@
”放在函数或类的定义行之前。- 要想使用装饰器来装饰一个函数或类,必须先定义这个装饰器。在python中,定义装饰器的格式与定义普通函数的格式完全一致,只不过装饰器函数的参数必须要有函数或类对象。
- 然后再装饰器函数中重新定义一个新的函数或类,并且再其中执行某些功能前后使用被装饰的函数或类。
- 最后返回这个新定义的函数或类。
def func1(func): #func为被装饰的函数对象,符合“装饰器函数的参数必须要有函数对象或类对象”这一条件
def func2(x,y): #func2()为再装饰函数中重新定义的一个函数,并返回了被装饰的函数func
x += 1
y += 2
return func(x,y)
return func2 #最后返回这个新定义的函数
@func1 #使用装饰器func1来装饰函数Sum
def Sum(a,b):
print('结果为:', a + b)
Sum(1,2)
代码解读
- 装饰器@func1其实可以看作func1(Sum)(a,b),它将函数Sum作为函数对象传给了func1,然后func1返回一个新定义的函数对象func2,故可以理解为
func1(Sum) == func2
。 - Sum的参数a和b传给了func2的x和y,故又可以理解为
func1(Sum)(a,b) == func2(a,b) == func2(x,y)
,之后发生新定义的函数func2的调用。 - 又新定义的函数返回的是被装饰函数func的调用,而此时的func就是实参Sum函数,故此处发生了Sum函数的调用,而调用的参数a和b来源于变化后的x和y,故装饰器使函数Sum加入了一些新的功能
(这些功能包装在新定义的函数func2中)
。 - 综上可总结出下列关系:
@func1 == func1(Sum)(a,b) == func2(a,b) == func2(x,y) == func(x,y)
。
运算结果
结果为: 6
参考文献
异步图书 // Python编程——从入门到精通 叶维忠编著 人民邮电出版社