Step1
先从一个小例子开始,然后逐步引出装饰器的作用。
def name(name = 'James'):
print('My name is {}.'.format(name))
name()
输出结果为:
My name is James.
如果此时想在打印“My name is...”之前加上打招呼的语句,且不能修改name()函数,就可以使用装饰器。
def greeting(func):
def wrapper(*args, **kargs):
print('Nice to meet you!')
return func(*args, **kargs)
return wrapper
@greeting
def name(name = 'James'):
print('My name is {}.'.format(name))
name()
print(name.__name__)
输出结果为:
Nice to meet you!
My name is James.
wrapper
Step2
单步运行这段加入装饰器的代码(使用spyder IDE),看看装饰器是如何工作的。
运行到def name(name = 'James')这行后,执行step in操作,立即跳转到def greeting(func)这行,在ipdb中输入func(),返回结果为:
ipdb> func()
My name is James.
说明greeting()内带入的参数是name()函数。
再单步运行到return wrapper这行,在ipdb中输入wrapper,返回结果为(每次返回的地址都不一样,下同):
ipdb> wrapper
<function greeting.<locals>.wrapper at 0x0A9E34B0>
然后就会跳出greeting(func)函数。继续执行,直到运行完name()这行代码后,然后就会输出:
Nice to meet you!
My name is James.
此时,在ipdb中输入name,返回结果为:
ipdb> name
<function greeting.<locals>.wrapper at 0x0A9E34B0>
可以看到wrapper和name的返回结果一致(虽然每次执行后at后面的地址会变,但两者的值一定是一样的),说明wrapper和name是同一个函数。
执行完最后一行后,显示:
wrapper
明明是name()函数,为何函数名变成了wrapper?
Step3
我们重头再回顾下刚才这段代码,然后理解一下装饰器的工作原理。
def greeting(func):
def wrapper(*args, **kargs):
print('Nice to meet you!')
return func(*args, **kargs)
return wrapper
@greeting
def name(name = 'James'):
print('My name is {}.'.format(name))
name()
print(name.__name__)
- @后面紧跟一个函数(此处是greeting()函数),这个函数必须有定义
- @greeting这行后紧跟一个函数,这个函数是greeting(func)函数的输入参数。在该例子中,greeting()函数的输入参数是name()函数,即func=name。所以,当执行func()的时候,实际上是执行了name()函数
- 在greeting()函数内定义了wrapper()函数,并返回wrapper()函数,这个返回值赋值给了同名的name,即执行完 greeting(name)后,将返回值wrapper赋值给name,name这个函数名已经不再属于原来那个name()函数了,而是属于wrapper()函数。这就可以回答为何print(name.__name__)返回的是wrapper而不是name
- 执行wrapper()函数的时候,func实际上是最原始的name()函数,先输出"Nice to meet you!",再调用func()函数——原始的name()函数,这样就实现了不改变来name函数并先输出期望的内容
为了解决name.__name__的值是wrapper而不是name,可以使用两个方法:
a. 在wrapper()函数内增加一行“wrapper.__name__ = func.__name__”:
def greeting(func):
def wrapper(*args, **kargs):
wrapper.__name__ = func.__name__
print('Nice to meet you!')
return func(*args, **kargs)
return wrapper
b. 使用python内置的functools.wraps:
import functools
def greeting(func):
@functools.wraps(func)
def wrapper(*args, **kargs):
# wrapper.__name__ = func.__name__
print('Nice to meet you!')
return func(*args, **kargs)
return wrapper
Step4
希望按照个人意愿随意输出打招呼的话,而不仅仅只是“Nice to meet you!”,可以在@greeting后面加入参数,例如@greeting("Hello, everyone!"),此时,就需要在greeting()函数中增加一层。
import functools
def greeting(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kargs):
print(text)
return func(*args, **kargs)
return wrapper
return decorator
@greeting("Hello, everyone!")
def name(name = 'James'):
print('My name is {}.'.format(name))
name()
print(name.__name__)
可以看到,greeting()函数的输入参数是打招呼的话,而不是@greeting下面一行的name。执行完greeting()后,返回decorator()函数,该函数带有输入参数,为name()函数。
@greeting("Hello, everyone!")
def name(name = 'James'):
上面这两句相当于:
decorator(name)
最后返回:
name=wrapper()
执行name()相当于执行wrapper()
Step5
目前有一个需求,同时支持@greeting和@greeting()两种装饰器。
import functools
def greeting(text_or_func):
if not isinstance(text_or_func, str):
@functools.wraps(text_or_func)
def wrapper(*args, **kargs):
print('Nice to meet you!')
text_or_func(*args, **kargs)
return wrapper
if isinstance(text_or_func, str):
def decorator(text_or_func):
def wrapper(*args, **kargs):
print(text_or_func)
text_or_func(*args, **kargs)
return wrapper
return decorator
@greeting()
def name(name = 'James'):
print('My name is {}.'.format(name))
name()