7.3.4 装饰器
在理解了嵌套函数的基础上,请读者耐心阅读以下代码。
代码语言:javascript复制#coding:utf-8
'''
filename: decorate.py
'''
def book(name):
return f"the name of my book is {name}"
def p_decorate(func):
def wrapper(name):
return f"<p>{func(name)}</p>"
return wrapper
if __name__ == "__main__":
my_book = p_decorate(book) # (17)
result = my_book("PYTHON") # (18)
print(result)
先看执行结果,再解释程序。
代码语言:javascript复制% python decorate.py
<p>the name of my book is PYTHON</p>
函数 book()
是一个普通的函数,函数 p_decorate()
是嵌套函数,外层函数的参数 func
所引用的对象必须可执行,并且是 func(name)
形式,正好 book()
函数可以满足(其它满足要求的函数亦可,这里仅以 book()
为例)。根据对嵌套函数的理解,注释(17)得到了闭包,注释(18)执行写在 p_decorate()
函数里面的 wrapper()
函数对象。最后打印返回值。
注意观察注释(18)执行后的返回值,已经与 book()
函数中的返值不同。由此结果可以说,函数 book()
的行为(例如返回对象)经过注释(17)后发生了改变,即被函数 p_decorate()
改变了——这种改变并不是重写 book()
函数,而是基于该函数原内容的变化。就如同一个女子或是“对镜贴花黄”或是“云鬓蓬松眉黛浅”,皆因化妆不同而看起来有不同的状态。于是可将函数 p_decorate()
类比于“花黄”,也给它取了一个形象的名字,称它是 book()
函数的装饰器(Decorator)。根据注释(17)可知,装饰器包裹一个函数之后,即改变其行为。
英国计算机科学家彼得·约翰·兰丁在研究闭包的同时(参阅7.3.2节),发明了语法糖(Syntactic Sugar)这个术语,意指编程语言中的一种语法,该语法只是让程序更简洁,可读性更强,并不影响功能。Python 语言中有以 @symbol
的形式实现装饰器语法糖。
将 decorate.py
中的程序修订如下,注意其中语法糖的运用。
#coding:utf-8
'''
filename: decorate.py
'''
def p_decorate(func):
def wrapper(name):
return f"<p>{func(name)}</p>"
return wrapper
@p_decorate # (19)
def book(name):
return f"the name of my book is {name}"
if __name__ == "__main__":
# my_book = p_decorate(book)
# result = my_book("PYTHON")
result = book("Learn Python") # (20)
print(result)
函数 p_decorate()
必须在 book()
之前,然后在函数 book()
上面增加了注释(19),即装饰器语法糖,再用注释(20)调用 book()
函数,其效果与修改前的程序等同。
% python decorate.py
<p>the name of my book is Learn Python</p>
甚至于可以编写多个装饰器,以语法糖的形式,将其作用于一个函数,例如:
代码语言:javascript复制#coding:utf-8
'''
filename: moresugar.py
'''
def p_decorate(func):
def wrapper(name):
return f"<p>{func(name)}</p>"
return wrapper
def div_decorate(func):
def wrapper(name):
return f"<div>{func(name)}</div>"
return wrapper
@div_decorate
@p_decorate
def book(name):
return f"the name of my book is {name}"
if __name__ == "__main__":
result = book("PYTHON")
print(result)
执行结果是:
代码语言:javascript复制% python moresugar.py
<div><p>the name of my book is PYTHON</p></div>
学习了装饰器和语法糖的基本用法之后,就要看看它们在实践中如何使用。下面就编写一个用于测量函数执行时间的装饰器。
代码语言:javascript复制#coding:utf-8
'''
filename: timing.py
'''
import time
def timing_func(func):
def wrapper():
start = time.time()
func()
stop = time.time()
return (stop - start)
return wrapper
@timing_func
def test_list_append():
lst = []
for i in range(0, 100000):
lst.append(i)
@timing_func
def test_list_compre():
[i for i in range(0, 100000)]
if __name__ == "__main__":
a = test_list_append()
c = test_list_compre()
print("test list append time:", a)
print("test list comprehension time:", c)
print("append/compre:", round(a/c, 3))
程序执行结果(不同性能的计算机,以下结果的数值可能有所不同):
代码语言:javascript复制% python timing.py
test list append time: 0.012425422668457031
test list comprehension time: 0.004575967788696289
append/compre: 2.715
将装饰器语法糖分别作用于两个函数,测量它们的执行时间。不仅程序显得简洁,且可读性强。另外,执行结果再次说明列表解析是必须要掌握的(参阅第6章6.4节)。
虽然我们是在嵌套函数的基础上引入了装饰器,但装饰器并不完全等同于嵌套函数,普通函数也能够作为装饰器,比如:
代码语言:javascript复制#coding:utf-8
'''
filename: decosimplyfunc.py
'''
import random
PLUGINS = dict() # (21)
def register(func):
PLUGINS[func.__name__] = func
return func
@register
def say_hello(name):
return f"Hello {name}"
@register
def be_awesome(name):
return f"Yo {name}, together we are the awesomest!"
def randomly_greet(name):
greeter, greeter_func = random.choice(list(PLUGINS.items()))
print(f"Using {greeter!r}")
return greeter_func(name)
print(PLUGINS)
calling_func = randomly_greet('laoqi')
print(calling_func)
先看执行结果,再解释代码:
代码语言:javascript复制% python decosimplyfunc.py
{'say_hello': <function say_hello at 0x7ff4b2d52700>, 'be_awesome': <function be_awesome at 0x7ff4b2d52790>}
Using 'say_hello'
Hello laoqi
# 再次执行
% python decosimplyfunc.py
{'say_hello': <function say_hello at 0x7fb36d652700>, 'be_awesome': <function be_awesome at 0x7fb36d652790>}
Using 'be_awesome'
Yo laoqi, together we are the awesomest!
注释(21)中以 PLUGINS
命名变量,通常用它表示“全局变量”,即在整个程序中都使用的变量。这也是约定俗成,并非强制。
以函数 register()
为装饰器,用语法糖的形式“装饰”了函数 say_hello()
和 be_awesom()
,则 PLUGINS
引用的字典中含有这两个函数对象(请观察执行结果)。函数randomly_greet()
从字典中随机选出一个函数,并执行。
这里仅介绍了装饰器的初步知识,读者在理解本节内容基础上,可以于此后对相关应用进行深入研究。