装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。
装饰器基础知识
首先看一下这段代码
def deco(fn):
print "I am %s!" % fn.__name__
@deco
def func():
pass
# output
I am func!
# 没有执行func 函数 但是 deco 被执行了
在用某个@decorator来修饰某个函数func时
@decorator
def func():
pass
其解释器会解释成下面这样的语句:
func = decorator(func)
其实就是把一个函数当参数传到另一个函数中,然后再回调,但是值得注意的是装饰器必须返回一个函数给func
装饰器的一大特性是,能把被装饰的函数替换成其他函数。第二大特性是,装饰器在加载模块时立即执行。
装饰器何时执行
装饰器的一个关键特性是,它们在被装饰的函数定义后立即运行。这通常在导入是(python 加载模块时)。
看下下面的示例:
registry = [] # registry 保存被@register 装饰的函数的引用
def register(func): # register 的参数是一个函数
print('running register(%s)' % func) # 打印被装饰的函数
registry.append(func) # 把 func 存入 `registery`
return func # 返回 func:必须返回函数,这里返回的函数与通过参数传入的一样
@register # `f1` 和 `f2`被 `@register` 装饰
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3(): # <7>
print('running f3()')
def main(): # main 打印 `registry`,然后调用 f1()、f2()和 f3()
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main() # <9>
运行代码结果如下:
running register()
running register()
running main()
registry -> [, ]
running f1()
running f2()
running f3()
从结果可以发现register 在模块中其他函数之前运行了两次。调用 register 时,传给它的参数是被装饰的函数(例如)。
看完上边的示例我们知道,函数被装饰器装饰后会变成装饰器函数的一个参数,那这时就不得不说变量的作用域了。
变量作用域
先看下下边这段代码:
def f1(a):
print(locals())
print(a)
print(b)
f1(3)
# output
{'a': 3}
3
Traceback(most recent call last):
File "", line 1, in
File "", line 3, in f1
NameError: global name 'b' is not defined
这里的错误是因为全局变量 b 没有定义,如果我们先在函数外部给 b 赋值,再调用这个方法就不会报错了。
函数运行时会创建一个新的作用域(命名空间)。函数的命名空间随着函数调用开始而开始,结束而销毁。
这个例子中 f1 的命名空间中只有 {'a': 3},所以 b 会被认为是全局变量。
再看一个例子:
b = 6
def f2(a):
print(a)
print(globals())
print(locals())
print(b)
b = 9
f2(3)
# output
3
{
'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': <_frozen_importlib_external.sourcefileloader object at>,
'__spec__': None,
'__annotations__': {},
'__builtins__': ,
'__file__': '~/var_local.py',
'__cached__': None,
'b': 6,
'f2':
}
{'a': 3}
3
Traceback(most recent call last):
File "", line 1, in
File "", line 3, in f1
UnboundLocalError: local variable 'b' referenced before assignment
这个例子和上一个例子不同是,我现在函数外部定义了全局变量b,但是执行f2 这个方法并没有打印6,这是为什么呢?
这是因为执行函数时 Python 会尝试从局部变量中获取 b,函数对于已经引用但未赋值的变量并不会自动声明为局部变量,所以解释器发现后边的赋值之前有引用就会抛出 UnboundLocalError 错误。
Python 不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。
如果要让解释器把b当做全局变量,要使用global声明:
b = 6
def f3(a):
global b
print(a)
print(b)
b = 9
f2(3)
# output
3