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 协议》,转载必须注明作者和本文链接