开源图书《Python完全自学教程》7.3.4装饰器

2022-07-06 16:04:56 浏览数 (2)

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 中的程序修订如下,注意其中语法糖的运用。

代码语言:javascript复制
#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() 函数,其效果与修改前的程序等同。

代码语言:javascript复制
% 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() 从字典中随机选出一个函数,并执行。

这里仅介绍了装饰器的初步知识,读者在理解本节内容基础上,可以于此后对相关应用进行深入研究。

0 人点赞