最近在重新在学习 Python 进阶的内容。整理一下关于装饰器(decorator)的一些知识。在解释装饰器前,先花一点时间总结一些关于函数的知识点。
这篇文章主要参考这个教程:Python 进阶,是《Intermediate Python》的中译本。
在 Python 中,所有的函数都是对象。如下面的例子中,函数可以直接赋值给变量。即使删除了原来的函数,但是变量还是保持了原函数的性质。
def hi(name):
return "hi," + name
greet = hi
print(greet("caoqi95"))
--------------------------------------
Outputs:
hi, caoqi95
--------------------------------------
del hi
print(hi("caoqi95"))
--------------------------------------
NameError: name 'hi' is not defined
--------------------------------------
print(greet("caoqi95"))
--------------------------------------
Outputs:
hi, caoqi95
--------------------------------------
在一个函数中,再内嵌一个函数,就能完成函数嵌套。如下所示:
def hi(name='caoqi95'):
print("now you are inside the hi() function")
def greet():
return "now you are in the greet() function"
def welcome():
return "noe you are in the welcome() function"
print(greet())
print(welcome())
print("now you are back in the hi() function")
hi()
--------------------------------------
Outputs:
now you are inside the hi() function
now you are in the greet() function
noe you are in the welcome() function
now you are back in the hi() function
--------------------------------------
有些时候,不需要在一个函数里嵌套一个函数,我们也可以将其作为输出返回出来。如下面的代码所示,greet() 函数和 welcome() 函数都可以在一定条件下,被 hi() 函数返回。
def hi(name='caoqi95'):
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
if name == 'caoqi95':
return greet
else:
return welcome
a = hi()
print(a)
--------------------------------------
Outputs:
<function hi.<locals>.greet at 0x00000284FD782598>
--------------------------------------
上面展示了 a 指向 hi() 函数中的 greet() 函数,现在看看下面的结果:
print(a())
--------------------------------------
Outputs:
now you are in the greet() function
--------------------------------------
为什么会出现不一样的结果呢?因为当函数名后面存在一对小括号的时候,函数才会被执行;而如果没有这对小括号,那该函数就可以被到处传递,并且还可以赋值给别的变量而不去执行它。使用 hi()() 也会与上面一样的结果。
函数还能作为参数,传给另一个函数使用。如下代码所示,hi() 函数被传递给 doSomeThingBeforeHi(func) 函数使用。
def hi():
return "hi, caoqi95"
def doSomeThingBeforeHi(func):
print("I am doing some boring work before executing hi()")
print(func())
doSomeThingBeforeHi(hi)
--------------------------------------
Outputs:
"I am doing some boring work before executing hi()"
"hi, caoqi95"
--------------------------------------
前面回顾了函数的一些基本知识,现在开始学习装饰器的内容。在上一个例子中,其实我们已经创建了一个装饰器。现在稍微修改一下:
def a_new_decorator(a_func):
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after acecuting a_func()")
return wrapTheFunction
def a_function_requiring_decoration():
print("I am the funciton which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
# outputs: "I am the function which needs some decoration to remove my foul smell"
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
# now a_function_requiring_decoration is wrapped by wrapTheFunction()
a_function_requiring_decoration()
--------------------------------------
Outputs:
"I am doing some boring work before executing a_func()"
"I am the funciton which needs some decoration to remove my foul smell"
"I am doing some boring work before executing a_func()"
--------------------------------------
在第二步的时候,把 a_function_requiring_decoration 函数当作一个参数赋值给了 a_new_decorator() 函数。然后第三步的时候再添加上一对小括号来执行。
下面用 @ 形式,写成更专业一点的装饰器函数:
def a_new_decorator(a_func):
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after acecuting a_func()")
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
# Hey you ! Decorate me !
print("I am the funciton which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
# @a_new_decorator 就是下面方法的简便形式
# a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
看上去很完美,第一个装饰器完成了。但是,上面的装饰器仍然存在一个问题。下面打印函数的名字就能够知道问题出在哪儿了:
print(a_function_requiring_decoration.__name__)
查看执行的结果,就会发现打印出的名字并不是原来的名字,而是被 warpTheFunction 给取代了。不用怕,Python 这么万能,肯定会有解决方案的。方案如下:
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after acecuting a_func()")
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
"""Hey you ! Decorate me !"""
print("I am the funciton which needs some decoration to remove my foul smell")
print(a_function_requiring_decoration.__name__)
现在问题解决了,总结一下一个装饰器的模板:
from functools import wraps
def decorator_name(f):
@wraps(f) # 用于解决原函数名字被抹去的情况
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated
@decorator_name
def func():
return("Function is running")
can_run = True
print(func())
# Output: Function is running
can_run = False
print(func())
# Output: Function will not run
# 注意:@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。
# 这可以让我们在装饰器里面访问在装饰之前的函数的属性。
最后,再总结一下关于装饰器的内容。其实装饰器实现的就是将函数作为参数提供给其他的函数调用的作用。但是使用装饰器的话,会让代码看上去整洁简短,使代码符合 Python 的核心价值观。装饰器还可以带有参数,还可以作为一个类使用,非常的方便。