装饰器用于在源码中“标记”函数,以增强函数的行为。

我们先来看下面的例子,现有一个求和函数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 装饰器解析(一)的更多相关文章

  1. python装饰器的详细解析

    什么是装饰器? python装饰器(fuctional decorators)就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名(或类名)的情况下,给函数增加新的功能. 这个函数的特殊之处在于 ...

  2. 自创最精简的python装饰器

    个人心血原创,欢迎转载,请注明作者和出处.否则依法追究法律责任!!!! author:headsen  chen date:2018-03-21  10:37:52 代码: 代码解析过程:1,def ...

  3. Python高级特性: 12步轻松搞定Python装饰器

    12步轻松搞定Python装饰器 通过 Python 装饰器实现DRY(不重复代码)原则:  http://python.jobbole.com/84151/   基本上一开始很难搞定python的装 ...

  4. Python学习:11.Python装饰器讲解(二)

    回顾 上一节我们进行了Python简单装饰器的讲解,但是python的装饰器还有一部分高级的使用方式,这一节就针对python装饰器高级部分进行讲解. 为一个函数添加多个装饰器 今天,老板又交给你一个 ...

  5. (转)python装饰器二

    Python装饰器进阶之二 保存被装饰方法的元数据 什么是方法的元数据 举个栗子 def hello(): print('Hello, World.') print(dir(hello)) 结果如下: ...

  6. python 装饰器(四):装饰器基础(三)叠放装饰器,参数化装饰器

    叠放装饰器 示例 7-19 演示了叠放装饰器的方式:@lru_cache 应用到 @clock 装饰fibonacci 得到的结果上.在示例 7-21 中,模块中最后一个函数应用了两个 @htmliz ...

  7. Python装饰器、迭代器&生成器、re正则表达式、字符串格式化

    Python装饰器.迭代器&生成器.re正则表达式.字符串格式化 本章内容: 装饰器 迭代器 & 生成器 re 正则表达式 字符串格式化 装饰器 装饰器是一个很著名的设计模式,经常被用 ...

  8. Python装饰器:套层壳我变得更强了

    Python装饰器:套层壳我变得更强了 Python装饰器:套层壳我变得更强了 关于作用域和闭包可以聊点什么? 什么是作用域 什么是闭包 装饰器:套层壳我变得更强了 参考资料 昨天阅读了<Pyt ...

  9. 关于python装饰器

    关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...

  10. python装饰器通俗易懂的解释!

    1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...

随机推荐

  1. nginx.conf 配置解析及常用配置

    本文为博主原创,未经允许不得转载: nginx.conf 配置文件配置解析 #定义 Nginx 运行的用户和用户组.默认nginx的安装用户为 nobody user www www: #启动进程,通 ...

  2. 为R Markdown配置TinyTex编译环境

    技术背景 在前面一篇博客中,我们介绍了一些关于在Windows系统上安装R Studio来编写R Markdown,最后编译成Beamer的演示文档的过程.而在Windows系统的使用过程中发现,编译 ...

  3. 03-Tcl数学表达式及expr命令

    3 Tcl书写表达式及expr命令 Tcl提供了有效的数学运算和逻辑运算功能.通过expr可以实现对数学表达式的分析和计算. 3.1 数学与逻辑运算符 运算符 说明 - + ~ ! 一元减(取负).一 ...

  4. [转帖]什么是内存屏障? Why Memory Barriers ?

    要了解如何使用memory barrier,最好的方法是明白它为什么存在.CPU硬件设计为了提高指令的执行速度,增设了两个缓冲区(store buffer, invalidate queue).这个两 ...

  5. TiKV 服务部署的注意事项

    TiKV 服务部署的注意事项 背景 最近发现tikv总是会掉线 不知道是哪里触发了啥样子的bug. 所以想着使用systemd 管理一下, 至少在tikv宕机的时候能够拉起来服务. 二进制文件 pd- ...

  6. DellEMC 服务器安装ESXi的简单步骤

    DellEMC 服务器安装ESXi的简单步骤 背景 ESXi的镜像其实分为多种. 官方会发布一个版本的ISO. 然后会不定期进行升级, 解决安全,性能以及功能bug等. 7.0 为例的话 就有ESXi ...

  7. [转帖]Linux中查找大文件两种姿势

    https://rumenz.com/rumenbiji/linux-find-du-max-file.html 使用find命令查找大文件 find命令是Linux系统管理员工具库中最强大的工具之一 ...

  8. [转帖]AlertManager 配置邮箱告警

    http://www.mydlq.club/article/126/ 2022-12-02 13:17:00KUBERNETESPROMETHEUSALERTMANAGER 文章目录 一.邮箱告警说明 ...

  9. 【转帖】Java Full GC (Ergonomics) 的排查

    文章目录 1. Full GC (Ergonomics) 1.1 Java 进程一直进行 Full GC 1.2 Full GC 的原因 1.3 检查堆占用 2. 代码检查 3. 解决方式 1. Fu ...

  10. [转帖]腾讯北极星 Polaris 试用

    https://www.cnblogs.com/QIAOXINGXING001/p/15482012.html 了解.试用 昨天稀土开发者大会2021提到了腾讯开源的北极星, 试用一下; 官网: 北极 ...