Python 装饰器解析(一)
装饰器用于在源码中“标记”函数,以增强函数的行为。
我们先来看下面的例子,现有一个求和函数add,现在要求统计函数执行的时长
def add(a, b):
print(a+b)
最low的做法:
def add(a, b):
start = time.time()
print(a + b)
time.sleep(2)#模拟耗时操作
long = time.time() - start
print(f'共耗时{long}秒。')
这样做可以实现需求,但是对原函数做了修改,不仅增加了耦合性,扩展和复用也变得难以实现。
假如再增加一个记录日志的功能以及对程序中所有的函数都进行时长统计,想想就可怕。
那好办啊,我们可以这样写:
def timer(func,*args):
start = time.time()
func(*args)
time.sleep(2)#模拟耗时操作
long = time.time() - start
print(f'共耗时{long}秒。')
timer(add,1,2)
这样没有改变原函数吧?是的,但是改变了函数调用方式,每个调用add的地方都需要修改,这么做只是转嫁了矛盾而已。
又不能修改原函数,又不能改变调用方式,那该怎么办呢?装饰器是时候登场了。
在写装饰器之前先了解两个概念:高阶函数和闭包
高阶函数:接受函数为入参,或者把函数作为结果返回的函数。后者称之为嵌套函数。
闭包:指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。概念比较晦涩,简单来说就是嵌套函数引用了外层函数的变量。
嵌套函数和闭包可以理解为是同时存在的,上面的timer已经是高阶函数了,它接受函数作为入参,我们把它改造为嵌套函数实现装饰器:
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs) #此处拿到了被装饰的函数func
time.sleep(2)#模拟耗时操作
long = time.time() - start
print(f'共耗时{long}秒。')
return wrapper #返回内层函数的引用
@timer
def add(a, b):
print(a+b)
add(1, 2) #正常调用add
timer被我们改造成了装饰器,它接受被装饰函数为入参,返回内部嵌套函数的引用(注意:此处并未执行函数),内部嵌套函数wrapper持有被装饰函数的引用即func。
“@”是Python的语法糖,它的作用类似于:
add = timer(add) #此处返回的是timer.<locals>.wrapper函数引用
add(1, 2)
装饰器的加载到执行的流程:
模块加载 ->> 遇到@,执行timer函数,传入add函数 ->> 生成timer.<locals>.wrapper函数并命名为add,其实是覆盖了原同名函数 ->> 调用add(1, 2) ->> 去执行timer.<locals>.wrapper(1, 2) ->> wrapper内部持有原add函数引用(func),调用func(1, 2) ->>继续执行完wrapper函数
问:如果存在多个装饰器,执行顺序是什么样的呢?看下面的代码
def test1(func):
def wrapper(*args, **kwargs):
print('before test1 ...')
func(*args, **kwargs)
print('after test1 ...')
return wrapper #返回内层函数的引用
def test2(func):
def wrapper(*args, **kwargs):
print('before test2 ...')
func(*args, **kwargs)
print('after test2 ...')
return wrapper #返回内层函数的引用
@test2
@test1
def add(a, b):
print(a+b)
add(1, 2) #正常调用add
输出:
before test2 ...
before test1 ...
3
after test1 ...
after test2 ...
如果把add函数比喻为圆心,test1为近心端,test2为远心端,那么执行的过程就好比一颗子弹从远心端沿着直径的轨迹穿过圆心再从远心端穿出。
再形象一点,可以把装饰器想象成洋葱,由近及远对函数进行层层包裹,执行的时候就是拿一把刀从一侧开始切,直到切到另一侧结束。
理解了装饰器之后,我们可以思考一下,带参数的装饰器该怎么写呢?
我们知道装饰器最终返回的是嵌套函数的引用,只要记住这点,装饰器就任由我们发挥了。写一个带参数的装饰器:
def auth(permission):
def _auth(func):
def wrapper(*args, **kwargs):
print(f"验证权限[{permission}]...")
func(*args, **kwargs)
print("执行完毕...")
return wrapper
return _auth
@auth("add")
def add(a, b):
"""
求和运算
"""
print(a + b)
add(1, 2) # 正常调用add
输出:
验证权限[add]...
3
执行完毕...
有些同学要问了,经过装饰器之后的函数还是原来的函数吗?原来的函数肯定还存在的,只不过真正调用的是装饰后生成的新函数。
那岂不是打破了“不能修改原函数”的规则?
是的,看下面的示例:
print(add)
print(add.__name__)
print(add.__doc__)
输出:
<function auth.<locals>._auth.<locals>.wrapper at 0x10c871400>
wrapper
None
为了消除装饰器对原函数的影响,我们需要伪装成原函数,拥有原函数的属性,看起来就像是同一个人一样。
functools为我们提供了便捷的方式,只需这样:
def auth(permission):
def _auth(func):
@functools.wraps(func) # 注意此处
def wrapper(*args, **kwargs):
print(f"验证权限[{permission}]...")
func(*args, **kwargs)
print("执行完毕...")
return wrapper
return _auth
@auth("add")
def add(a, b):
"""
求和运算
"""
print(a + b)
print(add)
print(add.__name__)
print(add.__doc__)
输出:
<function add at 0x10997c488>
add
求和运算
functools.wraps对我们的装饰器函数进行了装饰之后,add表面上看起来还是add。
functools.wraps内部通过partial和update_wrapper对函数进行再加工,将原始被装饰函数(add)的属性拷贝给装饰器函数(wrapper)。内部实现原理我们下文分解。
总结:
1、装饰器原则:1)不能修改原函数 2)不能修改调用方式
2、装饰器通过嵌套函数和闭包实现
3、装饰器执行顺序:洋葱法则
4、装饰器通过语法糖“@”修饰
5、谨记装饰器返回的是持有被装饰函数引用的闭包函数的引用这条原则。
Python 装饰器解析(一)的更多相关文章
- python装饰器的详细解析
什么是装饰器? python装饰器(fuctional decorators)就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名(或类名)的情况下,给函数增加新的功能. 这个函数的特殊之处在于 ...
- 自创最精简的python装饰器
个人心血原创,欢迎转载,请注明作者和出处.否则依法追究法律责任!!!! author:headsen chen date:2018-03-21 10:37:52 代码: 代码解析过程:1,def ...
- Python高级特性: 12步轻松搞定Python装饰器
12步轻松搞定Python装饰器 通过 Python 装饰器实现DRY(不重复代码)原则: http://python.jobbole.com/84151/ 基本上一开始很难搞定python的装 ...
- Python学习:11.Python装饰器讲解(二)
回顾 上一节我们进行了Python简单装饰器的讲解,但是python的装饰器还有一部分高级的使用方式,这一节就针对python装饰器高级部分进行讲解. 为一个函数添加多个装饰器 今天,老板又交给你一个 ...
- (转)python装饰器二
Python装饰器进阶之二 保存被装饰方法的元数据 什么是方法的元数据 举个栗子 def hello(): print('Hello, World.') print(dir(hello)) 结果如下: ...
- python 装饰器(四):装饰器基础(三)叠放装饰器,参数化装饰器
叠放装饰器 示例 7-19 演示了叠放装饰器的方式:@lru_cache 应用到 @clock 装饰fibonacci 得到的结果上.在示例 7-21 中,模块中最后一个函数应用了两个 @htmliz ...
- Python装饰器、迭代器&生成器、re正则表达式、字符串格式化
Python装饰器.迭代器&生成器.re正则表达式.字符串格式化 本章内容: 装饰器 迭代器 & 生成器 re 正则表达式 字符串格式化 装饰器 装饰器是一个很著名的设计模式,经常被用 ...
- Python装饰器:套层壳我变得更强了
Python装饰器:套层壳我变得更强了 Python装饰器:套层壳我变得更强了 关于作用域和闭包可以聊点什么? 什么是作用域 什么是闭包 装饰器:套层壳我变得更强了 参考资料 昨天阅读了<Pyt ...
- 关于python装饰器
关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...
- python装饰器通俗易懂的解释!
1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...
随机推荐
- SV 数据类型
system verilog可以用于设计也可以进行验证 语法规则 SV新数据类型 SV数据类型 bit - 0-255 byte - -127 - 128 # 快速进行sv文件仿真 VCS -R -s ...
- -- spi flash 擦除接口调用HAL库不同函数的区别
[描述] 在使用STM32F429操作W25Q128时,为验证flash工作正常,做简单的读写数据校验,在擦除接口中使用 HAL_SPI_Transmit 方法一直工作异常,使用 HAL_SPI_Tr ...
- 程序&命名-执行环境
开发程序执行环境 系统级别 -- 编译器或解释器 程序级别 -- 命令行参数.配置文件 执行级别 -- 进程.线程.协程运行时上下文(树(命名空间 -- 函数-局部变量.包或模块-全局变量)) 命令行 ...
- 如何让你的.NET WebAPI程序支持HTTP3?
下面我将总结构建Http3的经验,以Token Gateway的项目为例,请注意使用Http3之前你需要知道它的限制, Windows Windows 11 版本 22000 或更高版本/Window ...
- [转帖]聊聊TPS、QPS、CPS概念和区别
https://cloud.tencent.com/developer/article/1859053 TPS 概念 TPS:是TransactionsPerSecond的缩写,也就是事务数/秒.它是 ...
- [转帖]Nginx 安全优化
目录 前言 1.使用 SSL/TLS 证书 2.使用安全密钥交换机制 3.禁用旧的 SSL/TLS 协议 4.禁用 SSL/TLS 弱密码套件 5.禁用不需要的 HTTP 方法 6.防止缓冲区溢出攻击 ...
- [转帖]centos6 free 和 centos 7的free 的差异与对比
目录 一 centos6 free 常用参数和含义 centos6 free 命令示例 free 值讲解 计算公式 二 centos7 free 常用的参数 centos7 free 命令示例 计算公 ...
- [转帖]Docker容器无法访问网络问题(网段冲突)
近日在使用docker在腾讯云服务器上部署项目 运行容器时死活访问不了网络,不论是外网还是内网. 最后找到原因是docker容器ip网段与服务器内网ip网段冲突导致的 使用此命令查看到 ifconfi ...
- shell补遗_一个巨简单的保证服务存活的脚本
Shell补遗 背景 公司一台机器总是会在没有更新补丁的情况下启动失败. 查看所有的配置都没有问题. 但是就是不启动 没办法,准备写一个检查进行启动. 最近写shell很少. 所以总结一下. 思路 判 ...
- 信创CPU与牙膏厂和按摩店CPU的简单对比
信创CPU与牙膏厂和按摩店CPU的简单对比 摘要 周天时学习验证了SPEC2006的工具. 晚上时写完了第一稿简单的使用 因为 SPEC 完整跑完非常漫长. 我想了一下短平快还是通过使用一个简单的 r ...