Python函数装饰器的使用教程
作者:自动化代码美学 发布时间:2022-10-26 03:01:06
目录
典型的函数装饰器
叠放装饰器
参数化装饰器
标准库中的装饰器
functools.wraps
functools.lru_cache
functools.singledispatch
小结
参考资料:
典型的函数装饰器
以下示例定义了一个装饰器,输出函数的运行时间:
函数装饰器和闭包紧密结合,入参func代表被装饰函数,通过自由变量绑定后,调用函数并返回结果。
使用clock装饰器:
import time
from clockdeco import clock
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
if __name__=='__main__':
print('*' * 40, 'Calling snooze(.123)')
snooze(.123)
print('*' * 40, 'Calling factorial(6)')
print('6! =', factorial(6)) # 6!指6的阶乘
输出结果:
这是装饰器的典型行为:把被装饰的函数换成新函数,二者接受相同的参数,而且返回被装饰的函数本该返回的值,同时还会做些额外操作。
值得注意的是factorial()是个递归函数,从结果来看,每次递归都用到了装饰器,打印了运行时间,这是因为如下代码:
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
等价于:
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
factorial = clock(factorial)
factorial引用的是clock(factorial)函数的返回值,也就是装饰器内部函数clocked,每次调用factorial(n),执行的都是clocked(n)。
叠放装饰器
@d1
@d2
def f():
print("f")
等价于:
def f():
print("f")
f = d1(d2(f))
参数化装饰器
怎么让装饰器接受参数呢?答案是:创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。
示例如下:
registry = set()
def register(active=True):
def decorate(func):
print('running register(active=%s)->decorate(%s)'
% (active, func))
if active:
registry.add(func)
else:
registry.discard(func)
return func
return decorate
@register(active=False)
def f1():
print('running f1()')
# 注意这里的调用
@register()
def f2():
print('running f2()')
def f3():
print('running f3()')
register是一个装饰器工厂函数,接受可选参数active默认为True,内部定义了一个装饰器decorate并返回。需要注意的是装饰器工厂函数,即使不传参数,也要加上小括号调用,比如@register()。
再看一个示例:
import time
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
# 装饰器工厂函数
def clock(fmt=DEFAULT_FMT):
# 真正的装饰器
def decorate(func):
# 包装被装饰的函数
def clocked(*_args):
t0 = time.time()
# _result是被装饰函数返回的真正结果
_result = func(*_args)
elapsed = time.time() - t0
name = func.__name__
args = ', '.join(repr(arg) for arg in _args)
result = repr(_result)
# **locals()返回clocked的局部变量
print(fmt.format(**locals()))
return _result
return clocked
return decorate
if __name__ == '__main__':
@clock()
def snooze(seconds):
time.sleep(seconds)
for i in range(3):
snooze(.123)
这是给典型的函数装饰器添加了参数fmt,装饰器工厂函数增加了一层嵌套,示例中一共有3个def。
标准库中的装饰器
Python内置了三个用于装饰方法的函数:property、classmethod和staticmethod,这会在将来的文章中讲到。本文介绍functools中的三个装饰器:functools.wraps、functools.lru_cache和functools.singledispatch。
functools.wraps
Python函数装饰器在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用(它能保留原有函数的名称和函数属性)。
示例,不加wraps:
def my_decorator(func):
def wrapper(*args, **kwargs):
'''decorator'''
print('Calling decorated function...')
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""Docstring"""
print('Called example function')
print(example.__name__, example.__doc__)
# 输出wrapper decorator
加wraps:
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
'''decorator'''
print('Calling decorated function...')
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""Docstring"""
print('Called example function')
print(example.__name__, example.__doc__)
# 输出example Docstring
functools.lru_cache
lru是Least Recently Used的缩写,它是一项优化技术,把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。
示例:
import functools
from clockdeco import clock
@functools.lru_cache()
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
if __name__=='__main__':
print(fibonacci(6))
优化了递归算法,执行时间会减半。
注意,lru_cache可以使用两个可选的参数来配置,它的签名如下:
functools.lru_cache(maxsize=128, typed=False)
maxsize:最大存储数量,缓存满了以后,旧的结果会被扔掉。
typed:如果设为True,那么会把不同参数类型得到的结果分开保存,即把通常认为相等的浮点数和整型参数(如1和1.0)区分开。
functools.singledispatch
Python3.4的新增语法,可以用来优化函数中的大量if/elif/elif。使用@singledispatch装饰的普通函数会变成泛函数:根据第一个参数的类型,以不同方式执行相同操作的一组函数。所以它叫做single dispatch,单分派。
根据多个参数进行分派,就是多分派了。
示例,生成HTML,显示不同类型的Python对象:
import html
def htmlize(obj):
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)
因为Python不支持重载方法或函数,所以就不能使用不同的签名定义htmlize的变体,只能把htmlize变成一个分派函数,使用if/elif/elif,调用专门的函数,比如htmlize_str、htmlize_int等。时间一长htmlize会变得很大,跟各个专门函数之间的耦合也很紧密,不便于模块扩展。
@singledispatch经过深思熟虑后加入到了标准库,来解决这类问题:
from functools import singledispatch
from collections import abc
import numbers
import html
@singledispatch
def htmlize(obj):
# 基函数 这里不用写if/elif/elif来分派了
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)
@htmlize.register(str)
def _(text):
# 专门函数
content = html.escape(text).replace('\n', '<br>\n')
return '<p>{0}</p>'.format(content)
@htmlize.register(numbers.Integral)
def _(n):
# 专门函数
return '<pre>{0} (0x{0:x})</pre>'.format(n)
@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
# 专门函数
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>' + inner + '</li>\n</ul>'
@singledispatch装饰了基函数。专门函数使用@<<base_function>>.register(<<type>>)装饰,它的名字不重要,命名为_,简单明了。
这样编写代码后,Python会根据第一个参数的类型,调用相应的专门函数。
小结
本文首先介绍了典型的函数装饰器:把被装饰的函数换成新函数,二者接受相同的参数,而且返回被装饰的函数本该返回的值,同时还会做些额外操作。接着介绍了装饰器的两个高级用法:叠放装饰器和参数化装饰器,它们都会增加函数的嵌套层级。最后介绍了3个标准库中的装饰器:保留原有函数属性的functools.wraps、缓存耗时的函数结果的functools.lru_cache和优化if/elif/elif代码的functools.singledispatch。
参考资料:
《流畅的Python》https://github.com/fluentpython/example-code/tree/master/07-closure-deco
https://blog.csdn.net/liuzonghao88/article/details/103586634
来源:https://www.cnblogs.com/df888/p/14833074.html
![](https://www.aspxhome.com/images/zang.png)
![](https://www.aspxhome.com/images/jiucuo.png)
猜你喜欢
- 后台实时监控服务器的CUP与内存占用率的场景很常见,虽然没做过,但是着手写代码之前我真没想到会花2个多小时才最终实现。网上虽然搜 PHP C
- 先了解什么是deferGo语言中的defer与return执行的先后顺序Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 d
- 在Keras中有两种深度学习的模型:序列模型(Sequential)和通用模型(Model)。差异在于不同的拓扑结构。序列模型 Sequen
- Psyco 是严格地在 Python 运行时进行操作的。也就是说,Python 源代码是通过 python 命令编译成字节码的,所用的方式和
- 🔓一. CRUDCRUD : Create,Retrieve,Update,Delete新增数据查询数据修改数据删除数据MySQL的工作就是
- 1. 为什么要使用正则表达式?首先,大家来看一个例子。一个文本文件里面存储了一些市场职位信息,格式如下所示:Python3 高级开发工程师
- Python 中提供了对时间日期的多种多样的处理方式,主要是在 time 和 datetime 这两个模块里。一、time 模块time 模
- 在国内利用Python从Internet上爬取数据时,有些网站或API接口被限速或屏蔽,这时使用代理可以加速爬取过程,减少请求失败,Pyth
- Mysql可以通过运算符来对表中数据进行运算,比如通过出生日期求年龄等运算符包括四类,分别是:算数运算符、比较运算符、逻辑运算符和位运算符算
- MySQL Select语句是怎么执行的? 最近在极客时间看丁奇大佬的《MySQL45讲》,真心觉得讲的不错,把其中
- 用户不想输入账号密码,一键登录 <label for="" @click="LoginDL&q
- 引言 今天和测试沟通一个百分比计算方式时遇到一个问题, 我在存储过程里用到了强转
- python3 manage.py makemigrations # 生成数据库迁移文件python3 manage.py migrate
- MySQL开启通用查询日志general log mysql打开general log之后,所有的查询语句都可以在general log文件
- 颜色目标检测就是根据物体的颜色快速进行目标定位。使用cv2.inRange函数设定合适的阈值,即可以选出合适的目标。建立项目colordet
- 在设计主键的时候往往需要考虑以下几点: 1.无意义性:此处无意义是从用户的角度来定义的。这种无意义在一定程度上也会减少数据库的信息冗余。常常
- 对于字典的操作,本篇介绍的是在其中添加值的方法,下面带来详细的介绍。1、通过键=值的方式进行添加。如果键存在,则会将旧的值进行覆盖,如果不存
- 1.图像金字塔①高斯金字塔向下采样,数据会越来越少,减少的方式是:将偶数行和列删除向上采样,数据会越来越多,将图像在每个方向上扩大为原来的两
- 一、导包案例我们导入第三方库,可以使用import。那我们现在有一个需求,我需要动态输入一个模块名,然后导入,这应该怎么做呢?#!/usr/
- 在网络浏览器软件中,可以Internet Explorer (IE)现在是一种标准的软件。可以看到,运行不同版本的Windows操作系统(和