Python装饰器与闭包

闭包是Python装饰器的基础。要理解闭包,先要了解Python中的变量作用域规则。

变量作用域规则

首先,在函数中是能访问全局变量的:

>>> a = 'global var' >>> def foo(): print(a) >>> foo() global var

然后,在一个嵌套函数中,内层函数能够访问在外层函数中定义的局部变量:

>>> def foo(): a = 'free var' def bar(): print(a) return bar >>> foo()() free var

闭包

上面的嵌套函数就是闭包。闭包是指延伸了作用域的函数,在其中能够访问未在函数定义体中定义的非全局变量。未在函数定义体中定义的非全局变量一般都是在嵌套函数中出现的。

上述示例中的变量a就是一个并未在函数bar中定义的非全局变量。对于bar来说,它有个专业名字,叫做自由变量

自由变量的名称可以在字节码对象中查看:

>>> bar = foo() >>> bar.__code__.co_freevars ('a',)

自由变量的值绑定在函数的__closure__属性中:

>>> bar.__closure__ (<cell at 0x000001CB2912DF48: str object at 0x000001CB291D3D70>,)

其中保存了对应自由变量的cell对象的序列,cell对象的cell_contents属性保存了变量的值:

>>> bar.__closure__[0].cell_contents 'free var'

这与JavaScript中闭包的行为是类似的,JavaScript中嵌套函数会将外层函数的活动对象添加到它的作用域链中。但与JavaScript不同的是,当Python函数中的全局变量或者自由变量是不可变对象(数字、字符串、元组等)时,是只能读取,无法更新的:

>>> a = 1 >>> def foo(): print(a) a += 1 >>> foo() UnboundLocalError: local variable 'a' referenced before assignment >>> def foo(): a = 1 def bar(): print(a) a += 1 return bar >>> foo()() UnboundLocalError: local variable 'a' referenced before assignment

两种情况下,都会报错。这并不是缺陷,而是Python的设计选择。Python不要求声明变量,但是会假定在函数定义体中赋值的变量是局部变量,以避免在不知情的情况下修改全局变量。

a += 1与a = a + 1相同,编译函数的定义体时,会将a当做局部变量,不会当做自由变量保存。然后尝试获取a的值时,发现a并没有绑定值,于是报错。

解决这个问题的办法,一是将变量置于一些可变对象,如列表、字典中:

def foo(): ns = {} ns['a'] = 1 def bar(): ns['a'] += 1 print (ns['a']) return bar

另外的方法就是使用global或者nonlocal将变量声明为全局变量或者自由变量:

>>> def foo(): a = 1 def bar(): nonlocal a a += 1 print(a) return bar >>> foo()() 2

当自由变量本身是可变对象时,是可以直接进行操作的:

def make_avg(): ls = [] def avg(x): ls.append(x) print(sum(ls)/len(ls)) return avg

装饰器

装饰器是可调用对象,参数一般是另一个函数。装饰器可以以某种方式增强被装饰函数的行为,然后返回被装饰的函数或者将其替换成一个新的函数。

一个最简单的不做任何额外行为的装饰器:

def decorate(func): return func

decorate函数就是一个最简单的装饰器,使用方法:

def target(): pass target = decorate(target)

Python为装饰器的使用提供了语法糖,可以简便的写为:

@decorate def target(): pass

导入时运行

装饰器一个很重要的特性是它是导入时(加载模块时)运行的:

def decorate(func): print('running decorator when import') return func @decorate def foo(): print('running foo') pass if __name__ == '__main__': print('start foo') foo()

结果:

running decorator when import start foo running foo

可以看到,装饰器是导入时运行的,而被装饰的函数是明确调用时运行的。

装饰器可以返回被装饰的函数本身,和运行时导入的特性结合起来,可以实现简单的注册器功能:

view_registry = [] def register(func): view_registry.append(func) return func @register def view1(): pass @register def view2(): pass def main(): print(view_registry) if __name__ == '__main__': main()

返回新函数

上述装饰器的例子都返回了被装饰的原函数,但装饰器的典型行为还是返回一个新函数:把被装饰的函数替换成新函数,新函数接受与原函数相同的参数,并且返回原函数本该返回的值。写法类似于:

def deco(func): def new_func(*args, **kwargs): return func(*args, **kwargs) return new_func

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/33cd502225d9227cb5ad5c621e299e15.html