Python 的装饰符

Python 的装饰符

上回我发了一篇文章谈到 functools 模块, 这回就来谈谈 Python 的装饰符 ‘@’.

装饰函数

这里我们先说明装饰函数, 针对某些函数我们想要在调用前和调用后作一些动作, 就必须加写一些语句来处理. 比如我们想要记录一个函数被调用的时间以及函数运行所发的时间.

>>> from datetime import datetime
>>>
>>> def circle_area(radius):
>>>     area = 3.14*radius**2
>>>     print(f'Area of circle with radius {radius} is {area}')
>>>     return area
>>>
>>> now = datetime.now()
>>> print(f'func called at {now}')
>>> circle_area(3)
>>> print(f'Execution time is {datetime.now()-now}')
func called at 2020-06-11 20:33:37.828129
Area of circle with radius 3 is 28.26
Execution time is 0:00:00.007016

当然这样可以达到我们要的结果, 但是如果有别的函数也要作同样的事呢? 那不是又要增加那些代码? 函数是用来处理这样问题. 因此我们就要写一个函数专门作这事.

>>> from datetime import datetime
>>>
>>> def record_time(func, *args, **kwargs):
>>>     now = datetime.now()
>>>     print(f'func called at {now}')
>>>     value = func(*args, **kwargs)
>>>     print(f'Execution time is {datetime.now()-now}')
>>>     return value
>>>
>>> def circle_area(radius):
>>>     area = 3.14*radius**2
>>>     print(f'Area of circle with radius {radius} is {area}')
>>>     return area
>>>
>>> record_time(circle_area, 3)
func called at 2020-06-11 20:40:32.256889
Area of circle with radius 3 is 28.26
Execution time is 0:00:00.008006

这样就可以在各个需要的函数来调用 record_time, 这个 record_time 就可以称作装饰函数.

装饰符

使用装饰函数, 还是有点复杂, 因为调用各个函数时, 都得加上装饰函数, 还要分割参数. 因此在 Python 中就有一个所谓的装饰符 ‘@’, 可以来帮我们简化这件事. 基本上, 就是建立一个新的函数, 来取代你想用的函数, 其中新的函数前后包含你想要作的事.

from datetime import datetime

def record_time(func):
    """ 建立一个新的函数 """
    def new_function(*args, **kwargs):
        now = datetime.now()
        print(f'func called at {now}')
        value = func(*args, **kwargs)
        print(f'Execution time is {datetime.now()-now}')
        return value
    """ 返回这个新的函数 """
    return new_function

""" 装饰符的使用 """
@record_time
def circle_area(radius):
    area = 3.14*radius**2
    print(f'Area of circle with radius {radius} is {area}')
    return area

circle_area(3)

该装饰符就放在函数定义前一行, 其意义等同于把 circle_area 函数更改为我们在 record_time 中所定义的新函数.

def circle_area(radius):
    area = 3.14*radius**2
    print(f'Area of circle with radius {radius} is {area}')
    return area

""" circle_area 被取代为 record_time 中定义的 decorator """
circle_area = record_time(circle_area)

所以我們表面上用的是 circle_area, 事實上, 它已經被改成我們所定義的另一個新函數.

装饰函数的参数

装饰符的使用有两种方式, 类似 function 代表函数本身, 而 function() 代表函数调用后的结果. 以下例为例子:

@record_time 表示提供一个函数 record_time 来作包装, 等同
    circle_area1 = record_time(circle_area1) 也就是 wrapper

@record_time(n=2) 表示调用 record_time(n=2) 的结果来取代原函数, 等同
    circle_area2 = record_time(n=2)(circle_area2)
>>> from datetime import datetime
>>>
>>> def record_time(_func=None, n=1):
>>>     """ n 代表重复的次数 """
>>>     def decorator(func):
>>>         def wrapper(*args, **kwargs):
>>>             now = datetime.now()
>>>             print(f'{func.__name__} called at {now}')
>>>             for i in range(n):
>>>                 value = func(*args, **kwargs)
>>>             print(f'Execution time is {datetime.now()-now}')
>>>             return value
>>>         return wrapper
>>>     if _func is None:
>>>         return decorator
>>>     else:
>>>         return decorator(_func)
>>>
>>> @record_time
>>> def circle_area1(radius):
>>>     area = 3.14*radius**2
>>>     print(f'Area of circle with radius {radius} is {area}')
>>>     return area
>>>
>>> @record_time(n=2)
>>> def circle_area2(radius):
>>>     area = 3.14*radius**2
>>>     print(f'Area of circle with radius {radius} is {area}')
>>>     return area
>>>
>>> circle_area1(3)
>>> circle_area2(3)

circle_area1 called at 2020-06-12 09:50:52.705339
Area of circle with radius 3 is 28.26
Execution time is 0:00:00.010033
circle_area2 called at 2020-06-12 09:50:52.718341
Area of circle with radius 3 is 28.26
Area of circle with radius 3 is 28.26
Execution time is 0:00:00.019950


装饰函数的属性

装饰函数可以加上属性, 用来记录状态. 如下例可以记录函数被调用的次数.
在本例子中, 如果装饰的函数不同, 其调用的次数都会被累加在一起. 如果你想区分不同函数被调用的次数, 可以自建一个以 function.__name__:count_value 为键值对的字典分开统计.

>>> def times(func):
>>>     def decorator(*args, **kwagrs):
>>>         times.count += 1
>>>         print(f'This function called {times.count} times.')
>>>         value = func(*args, **kwagrs)
>>>         return value
>>>     times.count = 0
>>>     return decorator
>>>
>>> @times
>>> def circle_area(radius):
>>>     area = 3.14*radius**2
>>>     print(f'Area of circle with radius {radius} is {area}')
>>>     return area
>>>
>>> circle_area(3)
>>> circle_area(4)
>>> circle_area(5)

This function called 1 times.
Area of circle with radius 3 is 28.26
This function called 2 times.
Area of circle with radius 4 is 50.24
This function called 3 times.
Area of circle with radius 5 is 78.5


装饰符的叠加使用

叠加使用装饰符, 以下例

>>> @decorator_third
>>> @decorator_second
>>> @decorator_first
>>> def circle_area(radius):
>>>     area = 3.14*radius**2
>>>     print(f'Area of circle with radius {radius} is {area}')
>>>     return area

其结果如同

circle_area = decorator_third(decorator_second(decorator_first(circle_area)))


类的装饰符

类的装饰符建立方式有两种, 一种类似函数的使用, 另一种为了可以调用, 必须建立一个 __call__ 函数, 这里就不细说了, 有兴趣的再自行看一下 Python 的文件说明.

函数的代表符

旣然函数都被取代了, 你看到的函数代表符就不再是原函数的名称

>>> circle_area
<function record_time.<locals>.new_function at 0x000000000742E9D0>

那怎么办呢, 一样, 再加个装饰符, 当然你可以自己设计, 也可以使用现成的@functools.wraps(func)

from datetime import datetime
import functools

def record_time(func):
    """ 新增装饰符 """
    @functools.wraps(func) 
    def new_function(*args, **kwargs):
        now = datetime.now()
        print(f'func called at {now}')
        value = func(*args, **kwargs)
        print(f'Execution time is {datetime.now()-now}')
        return value
    return new_function

@record_time
def circle_area(radius):
    area = 3.14*radius**2
    print(f'Area of​​ circle with radius {radius} is {area}')
    return area

circle_area(3)
>>> circle_area
<function circle_area at 0x00000000082EE940>
本作品采用《CC 协议》,转载必须注明作者和本文链接
Jason Yang
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!