转载:Python之修饰器 - 知乎 (zhihu.com)

什么是修饰器,为什么叫修饰器

修饰器英文是Decorator,

我们假设这样一种场景:古老的代码中有几个很是复杂的函数F1、F2、F3...,复杂到看都不想看,反正我们就是不想改这些函数,但是我们需要改造加功能,在这个函数的前后加功能,这个时候我们很容易就实现这个需求:

def hi():
"""hi func,假装是很复杂的函数"""
return 'hi' def aop(func):
"""aop func"""
print('before func')
print(func())
print('after func') if __name__ == '__main__':
aop(hi)

以上是很是简单的实现,利用Python参数可以传函数引用的特性,就可以实现了这种类似AOP的效果。

这段代码目前没有什么问题,接下来煎鱼加需求:需求为几十个函数都加上这样的前后的功能,而所有调用这些函数地方也要相应地升级。

看起来这个需求比较扯,偏偏这个需求却是较为广泛:在调用函数的前后加上log输出、在调用函数的前后计算调用时间、在调用函数的前后占用和释放资源等等。

一种比较笨的方法就是,为这几十个函数逐一添加一个入口函数,针对a函数添加一个a_aop函数,针对b函数添加一个b_aop函数...如此这样。问题也很明显:

  1. 工作量大
  2. 代码变得臃肿复杂
  3. 原代码有多处调用了这些函数,可以会升级不完全

于是接下来有请修饰器出场,修饰器可以统一地给这些函数加这样的功能:

def aop(func):
"""aop func"""
def wrapper():
"""wrapper func"""
print('before func')
func()
print('after func')
return wrapper @aop
def hi():
"""hi func"""
print('hi') @aop
def hello():
"""hello func"""
print('hello') if __name__ == '__main__':
hi()
hello()
 

以上aop函数就是修饰器的函数,使用该修饰器时只要在待加函数上一行加@修饰器函数名即可,如实例代码中就是@aop。

加上了@aop后,调用新功能的hi函数就喝原来的调用一样:就是hi()而不是aop(hi),也意味着所有调用这些函数的地方不需要修改就可以升级。

简单地来说,大概修饰器就是以上的这样子。

@是个什么

对于新手来说,上面例子中,@就是一样奇怪的东西:为什么这样子用就可以实现煎鱼需求的功能了。

其实我们还可以不用@,煎鱼换一种写法:

def hi():
"""hi func"""
print('hi') def aop(func):
"""aop func"""
def wrapper():
"""wrapper func"""
print('before func')
func()
print('after func')
return wrapper if __name__ == '__main__':
hi() print('') hi = aop(hi)
hi()

上面的例子中的aop函数就是之前说过的修饰器函数。

如例子main函数中第一次调用hi函数时,由于hi函数没叫修饰器,因此我们可以从输出结果中看到程序只输出了一个hi而没有前后功能。

然后煎鱼加了一个hi = aop(hi)后再调用hi函数,得到的输出结果和加修饰器的一样,换言之:

@aop 等效于hi = aop(hi)

因此,我们对于@,可以理解是,它通过闭包的方式把新函数的引用赋值给了原来函数的引用。

有点拗口。aop(hi)是新函数的引用,至于返回了引用的原因是aop函数中运用闭包返回了函数引用。而hi这个函数的引用,本来是指向旧函数的,通过hi = aop(hi)赋值后,就指向新函数了。

被调函数加参数

以上的例子中,我们都假设被调函数是无参的,如hi、hello函数都是无参的,我们再看一眼煎鱼刚才的写的修饰器函数:

def aop(func):
"""aop func"""
def wrapper():
"""wrapper func"""
print('before func')
func()
print('after func')
return wrapper

很明显,闭包函数wrapper中,调用被调函数用的是func(),是无参的。同时就意味着,如果func是一个带参数的函数,再用这个修饰器就会报错。

@aop
def hi_with_deco(a):
"""hi func"""
print('hi' + str(a)) if __name__ == '__main__':
# hi()
hi_with_deco(1)

就是参数的问题。这个时候,我们把修饰器函数改得通用一点即可,其中import了一个函数(也是修饰器函数):

from functools import wraps

def aop(func):
"""aop func"""
@wraps(func)
def wrap(*args, **kwargs):
print('before')
func(*args, **kwargs)
print('after') return wrap @aop
def hi(a, b, c):
"""hi func"""
print('test hi: %s, %s, %s' % (a, b, c)) @aop
def hello(a, b):
"""hello func"""
print('test hello: %s, %s' % (a, b)) if __name__ == '__main__':
hi(1, 2, 3)
hello('a', 'b')

这是一种很奇妙的东西,就是在写修饰器函数的时候,还用了别的修饰器函数。那也没什么,毕竟修饰器函数也是函数啊,有什么所谓。

带参数的修饰器

思路到了这里,煎鱼不禁思考一个问题:修饰器函数也是函数,那函数也是应该能传参的。函数传参的话,不同的参数可以输出不同的结果,那么,修饰器函数传参的话,不同的参数会怎么样呢?

其实很简单,修饰器函数不同的参数,能生成不同的修饰器啊。

如,我这次用这个修饰器是把时间日志打到test.log,而下次用修饰器的时候煎鱼希望是能打到test2.log。这样的需求,除了写两个修饰器函数外,还可以给修饰器加参数选项:

from functools import wraps

def aop_with_param(aop_test_str):
def aop(func):
"""aop func"""
@wraps(func)
def wrap(*args, **kwargs):
print('before ' + str(aop_test_str))
func(*args, **kwargs)
print('after ' + str(aop_test_str))
return wrap
return aop @aop_with_param('abc')
def hi(a, b, c):
"""hi func"""
print('test hi: %s, %s, %s' % (a, b, c)) @aop_with_param('pppppp')
def hi2(a, b, c):
"""hi func"""
print('test hi: %s, %s, %s' % (a, b, c)) if __name__ == '__main__':
hi(1, 2, 3)
print('')
hi2(2, 3, 4)

同样的,可以加一个参数,也可以加多个参数,这里就不说了。

修饰器类

大道同归,逻辑复杂了之后,人们都喜欢将函数的思维层面抽象上升到对象的层面。原因往往是对象能拥有多个函数,对象往往能管理更复杂的业务逻辑。

显然,修饰器函数也有对应的修饰器类。写起来也没什么难度,和之前的生成器一样简单:

from functools import wraps

class aop(object):
def __init__(self, aop_test_str):
self.aop_test_str = aop_test_str def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('before ' + self.aop_test_str)
func()
print('after ' + self.aop_test_str) return wrapper @aop('pppppp')
def hi():
print('hi')

看得出来,这个修饰器类也不过是多了个__call__函数,而这个__call__函数的内容和之前写的修饰器函数一个样!而使用这个修饰器的方法,和之前也一样,一样的如例子中的@aop('pppppp')。

甚至,煎鱼过于无聊,还试了一下继承的修饰器类:

class sub_aop(aop):
def __init__(self, sub_aop_str, *args, **kwargs):
self.sub_aop_str = sub_aop_str
super(sub_aop, self).__init__(*args, **kwargs) def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('before ' + self.sub_aop_str)
super(sub_aop, self).__call__(func)()
print('after ' + self.sub_aop_str)
return wrapper @sub_aop('ssssss', 'pppppp')
def hello():
print('hello') if __name__ == '__main__':
hello()

python 修饰器(decorator)的更多相关文章

  1. Python修饰器的函数式编程

    Python的修饰器的英文名叫Decorator,当你看到这个英文名的时候,你可能会把其跟Design Pattern里的Decorator搞混了,其实这是完全不同的两个东西.虽然好像,他们要干的事都 ...

  2. Python修饰器

    Python的修饰器的英文名叫Decorator,当你看到这个英文名的时候,你可能会把其跟Design Pattern里的Decorator搞混了,其实这是完全不同的两个东西.虽然好像,他们要干的事都 ...

  3. python修饰器(装饰器)以及wraps

    Python装饰器(decorator)是在程序开发中经常使用到的功能,合理使用装饰器,能让我们的程序如虎添翼. 装饰器的引入 初期及问题的诞生 假如现在在一个公司,有A B C三个业务部门,还有S一 ...

  4. Python修饰器的函数式编程(转)

    From:http://coolshell.cn/articles/11265.html 作者:陈皓 Python的修饰器的英文名叫Decorator,当你看到这个英文名的时候,你可能会把其跟Desi ...

  5. python 修饰器 最好的讲解

    Python的修饰器的英文名叫Decorator,修饰器就是对一个已有的模块做一些“修饰工作”,比如在现有的模块加上一些小装饰(一些小功能,这些小功能可能好多模块都会用到),但又不让这个小装饰(小功能 ...

  6. ES2017中的修饰器Decorator

    前面的话 修饰器(Decorator)是一个函数,用来修改类的行为.本文将详细介绍ES2017中的修饰器Decorator 概述 ES2017 引入了这项功能,目前 Babel 转码器已经支持Deco ...

  7. 谈谈python修饰器

    前言 对python的修饰器的理解一直停留在"使用修饰器把函数注册为事件的处理程序"的层次,也是一知半解:这样拖着不是办法,索性今天好好整理一下关于python修饰器的概念及用法. ...

  8. python函数修饰器(decorator)

    python语言本身具有丰富的功能和表达语法,其中修饰器是一个非常有用的功能.在设计模式中,decorator能够在无需直接使用子类的方式来动态地修正一个函数,类或者类的方法的功能.当你希望在不修改函 ...

  9. python 装饰器(decorator)

    装饰器(decorator) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 装饰器(decorator)是一种高级Python语 ...

随机推荐

  1. ☕【Java技术指南】「并发编程专题」针对于Guava RateLimiter限流器的入门到精通(含实战开发技巧)

    并发编程的三剑客 在开发高并发系统时有三剑客:缓存.降级和限流. 缓存 缓存的目的是提升系统访问速度和增大系统处理容量. 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题 ...

  2. vue随记

    (一)使用props传值: <HeadTitle name-data="100"></HeadTitle> props:['nameData'] 父组件传递 ...

  3. java中避免集合死链调用

    目录 1. 前言 2. 场景 3. 环境 3.1 开发环境准备 3.2 数据准备 3.2.1 Mysql数据库表及数据 3.2.2 redis库数据 4. 解决方式 5.完整代码 5.1Model 5 ...

  4. postgresql批量插入copy_from()的使用

    在批量插入postgresql时想使用同Mysql的语法时发现并不能使用: cursor.executemany("INSERT INTO persons VALUES (%d, %s, % ...

  5. 如何借助 JuiceFS 为 AI 模型训练提速 7 倍

    背景 海量且优质的数据集是一个好的 AI 模型的基石之一,如何存储.管理这些数据集,以及在模型训练时提升 I/O 效率一直都是 AI 平台工程师和算法科学家特别关注的事情.不论是单机训练还是分布式训练 ...

  6. Vite插件开发纪实:vite-plugin-monitor(下)

    前言 上一篇介绍了Vite启动,HMR等时间的获取. 但各阶段详细的耗时信息,只能通过debug的日志获取 本文就实现一下debug日志的拦截 插件效果预览 --debug做了什么 项目启动指令 vi ...

  7. 【深度学习】线性回归(Linear Regression)——原理、均方损失、小批量随机梯度下降

    1. 线性回归 回归(regression)问题指一类为一个或多个自变量与因变量之间关系建模的方法,通常用来表示输入和输出之间的关系. 机器学习领域中多数问题都与预测相关,当我们想预测一个数值时,就会 ...

  8. xLua自定义加载器

    xLua入门基础 环境配置 github下载xLua文件: xLua是腾讯开发,据说比较先进: 下载下来后将Plugins和XLua文件夹考进项目: Plugins多平台权限:XLua和C#交互: t ...

  9. JDK源码阅读(4):HashMap类阅读笔记

    HashMap public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, ...

  10. div标签的理解

    在HTML里面,div标签是一个块状元素,不会和其他元素排列在同一行,会默认和下面的元素换行,但是如果我们需要把几个div标签排在同一行,需要怎么做? 第一种:修改块状元素 源码: <div i ...