python装饰器详细剖析
文章目录
- 函数装饰器
- 基本函数装饰器
- 传参函数装饰器
- 类装饰器
- 基本类装饰器
- 传参类装饰器
- 装饰器执行顺序
- 内置装饰器
- @abstractmethod
- @property
- @classmethod
- @staticmethod
- 内置装饰器小结
- 装饰器属性还原
- 写在篇后
装饰器函数其实是这样一个接口约束,它 必须接受一个 callable 对象作为参数,然后返回一个 callable 对象,其作用就是为已经存在的函数或对象添加额外的功能。
函数装饰器
基本函数装饰器
Talk is cheap, show me the code.所以,下面先给出一个最简单的例子,再来解释装饰器原理。
def log_it(func):def wrapper(*args, **kwargs):print("[Debug]: enter function {}()".format(func.__name__))return func(*args, **kwargs) # if function func has return, remember to returnreturn wrapper@log_it
def add_number(*args):return sum(args)print(add_number(1, 2, 3, 4))
print(add_number.__name__)# 输出
[Debug]: enter function add_number()
10
wrapper
细心的你会发现,print(add_number.__name__)
输出的是wrapper,意味着装饰器的实际意思就是 add_number = log_it(add_number)
,所以执行add_number()
函数就相当于执行wrapper()
函数,而这也就是装饰器的原理啦。即通过传入一个函数将其包装成一个新的函数,赋予它额外的功能。
传参函数装饰器
装饰器还有更大的灵活性,例如带参数的装饰器,比如上面的例子中,你想控制print
语句的内容是动态的,那么你可以给它传入参数,代码如下:
def log_it2(level='Debug'):def wrapper(func):def inner_wrapper(*args, **kwargs):print("[{level}]: enter function {func}()".format(level=level,func=func.__name__))return func(*args, **kwargs)return inner_wrapperreturn wrapper@log_it2(level='Info')
def add_number2(*args):return sum(args)print(add_number2(1, 2, 3, 4, 5))# 输出
[Info]: enter function add_number2()
15
看到这里我估计有朋友要晕了,因为这个装饰器居然嵌套了三个函数定义,什么鬼?是不是瞬间感觉还不如学习C++,根本不存在函数嵌套,哈哈~~接下来来仔细分析一下:
- 首先我们要清楚相比第一个例子,我们多加了一层函数嵌套,且这一层函数嵌套就是也仅仅是为了给装饰器传递参数,那么不难想到
add_number2.__name__
应该是inner_wrapper(不信你可以输出看一看); - 基于第1点,我们应该可以推理出它的实际调用是
add_number2 = log_it2(debug='info')(add_number2)
, 从而说明add_number2.__name__
的值就是inner_wrapper.
类装饰器
类装饰器和函数装饰器一样,包括基本类装饰器和传参类装饰器两种类型,但是其形式略有不同,类装饰器必须实现魔法方法__call__
函数,关于__call__
函数以及更多魔法方法的用法请参考Python面向对象、魔法方法
基本类装饰器
示例代码如下:
class LogIt(object):def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):print("[DEBUG]: enter function {func}()".format(func=self.func.__name__))return self.func(*args, **kwargs)@LogIt
def add_number(*args):return sum(args)print(add_number(1, 2, 3, 4))
我上面说形式上略有不同,可这一看,好像却是大有不同,这又是怎么实现的呢?首先LogIt类实现了构造方法__init__(self,func)
,它接收一个函数,这看起来很正常;但是,它还是实现了__call__
方法并返回了函数func
,这不正是我们开篇说的,接受一个函数并返回一个函数吗?这就是它的实现原理。我们可以验证一下:
>>>add_number
<__main__.LogIt object at 0x10e4dd7f0>
看上面的输出,add_number
变成一个函数了,所以我们不难想象,它实际过程是 add_number = LogIt(add_number),如此一来其自然而然就是LogIt的一个实例了。
传参类装饰器
那么类装饰器要怎么传递参数呢?请看下面的示例代码:
class LogIt2(object):def __init__(self, level='INFO'):self.level = leveldef __call__(self, func):def wrapper(*args, **kwargs):print("[{level}]: enter function {func}()".format(level=self.level,func=func.__name__))return func(*args, **kwargs)return wrapper@LogIt2(level='INFO')
def add_number2(*args):return sum(args)print(add_number2(1, 2, 3, 4, 5))
如果你融会贯通了上面所蕴含的思想,应该不难推理出它的包装过程就是:add_number2 = LogIt('INFO')(add_number2)
,所以add_number2
此时应该是实际上是wrapper
函数。怎么知道我说的是对的呢?
>>> add_number2.__name__
wrapper
以上就是装饰器的一些基本使用了,多看几遍,多实践几次,你一定可以看懂!
装饰器执行顺序
以上我们只讨论了一个装饰器的使用,如果同时使用多个装饰器,会是怎样的执行流程呢?我们先直接上一个例子,看一看:
def decorator_a(func):print('Get in decorator_a')def inner_a(*args, **kwargs):print('Get in inner_a')return func(*args, **kwargs)return inner_adef decorator_b(func):print('Get in decorator_b')def inner_b(*args, **kwargs):print('Get in inner_b')return func(*args, **kwargs)return inner_b@decorator_b
@decorator_a
def f(x):print('Get in f')return x * 2res = f(1)
print('res:', res)# 输出结果
# Get in decorator_a
# Get in decorator_b
# Get in inner_b
# Get in inner_a
# Get in f
# res: 2
如果你是第一次看装饰器,且你的预想中也是这个输出,那么恭喜你,你一定是996ICU友情链接的天选之人;如果你看的目瞪口呆,其实也不要紧,只要你有995ICU友情链接2的精神,你也可以是王者。好吧,回到正题,其实关于多装饰器执行顺序的规则就是从里到外顺序执行,即最先调用最里层的装饰器,最后调用最外层的装饰器。其调用过程为:
f = decorator_b(decorator_a(f))
且最后f
的形式为:
# 注意下面不是可执行代码,缩进是为了表示逻辑关系
def inner_b(*args, **kwargs):print('Get in inner_b')# -----------inner_a-------------print('Get in inner_a')# -----------f-------------print('Get in f')return x * 2
我觉得,如果你看懂了上面这一段"伪代码",那么你就真的理解了装饰器原理。如果没看懂,不要紧,试着多看几遍就好;就好比心情不好,那就去吃一顿火锅,如果不够,那就再来一顿!
内置装饰器
非常抱歉,本人才疏学浅,此处存在错误,经过严肃探究,发现property、classmethod、staticmethod不是装饰器,只是使用形式类似装饰器,实际上是描述器,文章其他部分应该没有问题。关于这三个描述器的理解,请见python描述器深度理解
在python中有以下几个常见的内置装饰器,它们都和python面向对象编程有关,下面分别做简要介绍:
@abstractmethod
这是python中抽象类"虚方法"的定义方式,一个类中如果存在@abc.abstractmethod装饰的方法,那么其不可以实例化对象,且继承的子类必须实现@abc.abstractmethod装饰的方法。
import abc
class A(object):__metaclass__ = abc.ABCMeta@abc.abstractmethoddef load(self, _input):pass@abc.abstractmethoddef save(self, output, data):passclass B(A):def load(self, _input):return _input.read()def save(self, output, data):return output.write(data)if __name__ == '__main__':print(issubclass(B, A))print(isinstance(B(), A))print(A.__subclasses__())# 输出
True
True
[<class '__main__.B'>]
@property
类属性有三个装饰器: setter
, getter
, deleter
,它们都是在 property ()
的基础上做了一些封装,其中getter
装饰器和不带 getter
的属性装饰器效果是一样的。该特性最重要的功能之一就是能实现属性的参数检验。
class Student(object):@propertydef score(self):return self._score@score.setterdef score(self, value):if not isinstance(value, int):raise ValueError('score must be an integer!')if value < 0 or value > 100:raise ValueError('score must between 0 ~ 100!')self._score = value@score.deleterdef score(self):del self._scores = Student()
s.score = 10
print(s.score)
@classmethod
被@classmethod
装饰的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。
class A(object):# 属性默认为类属性(可以给直接被类本身调用)num = "类属性"# 实例化方法(必须实例化类之后才能被调用)def func1(self): # self : 表示实例化类后的地址idprint("func1")print(self)# 类方法(不需要实例化类就可以被类本身调用)@classmethoddef func2(cls): # cls : 表示没用被实例化的类本身print("func2")print(cls)print(cls.num)cls().func1()# 不传递传递默认self参数的方法(该方法也是可以直接被类调用的,但是这样做不标准)def func3():print("func3")print(A.num) # 属性是可以直接用类本身调用的# A.func1() 这样调用是会报错:因为func1()调用时需要默认传递实例化类后的地址id参数,如果不实例化类是无法调用的
A.func2()
A.func3()
@staticmethod
被@staticmethod
修饰的方法是静态方法,静态方法的参数可以根据业务需求传入,没有固定参数;然而前面的实例化方法第一个参数必须是self
,类方法第一个参数必须是cls
。静态方法,跟普通函数没什么区别,与类和实例都没有所谓的绑定关系,它只不过是碰巧存在类中的一个函数而已,不论是通过类还是实例都可以引用该方法。之所以将其放在类中,是因为该方法仅为这个类服务。
class Method(object):def __init__(self, data):pass@staticmethoddef static_method():print "This is static method in class Method"
内置装饰器小结
为了更好的理解辨明实例方法、类方法、静态方法、抽象方法,我再举一个例子,从本质上来理一理它们之间的关系:
import abc
class ICU996():@staticmethoddef static_m(self):pass@classmethoddef class_m(cls):passdef instance_m(self):pass@abc.abstractmethoddef abstract_m(self):pass>>>icu = ICU996()
>>>icu.static_m
<function ICU996.static_m at 0x11af930d0>
>>>icu.class_m
<bound method ICU996.class_m of <class 'test.ICU996'>>
>>>icu.instance_m
<bound method ICU996.instance_m of <test.ICU996 object at 0x11af54978>>
>>>icu.abstract_m
<bound method ICU996.abstract_m of <test.ICU996 object at 0x11af54978>>
从上面的输出结果不难看出,实例化方法和抽象方法本质上是一样的,都是绑定在实例上的方法;而类方法这是绑定在类上的方法;静态方法则实质上就是一个不同函数。
装饰器属性还原
最后,我们来解决前面的出现的一个问题,在第一个实例中,我们发现被装饰的函数add_number.__name__
变成了wrapper,那么如果我们并不想让这样的事情发生,我们该怎么做呢?其实,这一点,可以用内置的装饰器wraps
来实现:
from functools import wrapsdef log_it(func):@wraps(func) # comment this line to see the diffdef wrapper(*args, **kwargs):print("[Debug]: enter function {}()".format(func.__name__))return func(*args, **kwargs)return wrapper@log_it
def add_number(*args):"""add numbers in tuple:param args: should be numbers:return: the sum of tuple"""return sum(args)print(add_number.__name__, add_number.__doc__)
# 输出
add_number add numbers in tuple:param args: should be numbers:return: the sum of tuple
它不仅可以使函数名保持不变,还可以保持函数原有的doc string。好,那么是不是应该思考一下它是怎样实现的呢?
from functools import partialWRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')
WRAPPER_UPDATES = ('__dict__',)def update_wrapper(wrapper,wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES):for attr in assigned:try:value = getattr(wrapped, attr)except AttributeError:passelse:setattr(wrapper, attr, value)for attr in updated:getattr(wrapper, attr).update(getattr(wrapped, attr, {}))# Issue #17482: set __wrapped__ last so we don't inadvertently copy it# from the wrapped function when updating __dict__wrapper.__wrapped__ = wrapped# Return the wrapper so this can be used as a decorator via partial()return wrapperdef wraps(wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES):return partial(update_wrapper, wrapped=wrapped,assigned=assigned, updated=updated)
这是python官方的实现方式,关于__module__
,__name__
等特殊属性,请参考python特殊属性、魔法方法;这里包装的过程简化来看就是add_number = update_wrapper(wrapper, add_number)
,其中update_wrapper
的功能是更新wrapper函数的一些特殊属性。
写在篇后
装饰器是python的一大难点,它本质上就是一个函数,它可以让其他函数在不需要变动的情况下增加额外的功能。装饰器常用于有切面的应用场景,如插入日志、性能测试等场景。多看、多试、多用,装饰器,其实也不难。
python装饰器详细剖析相关推荐
- Python装饰器详解,详细介绍它的应用场景
装饰器的应用场景 附加功能 数据的清理或添加: 函数参数类型验证 @require_ints 类似请求前拦截 数据格式转换 将函数返回字典改为 JSON/YAML 类似响应后篡改 为函数提供额外的数据 ...
- python 装饰器 参数-python装饰器的详细解析
什么是装饰器? python装饰器(fuctional decorators)就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名(或类名)的情况下,给函数增加新的功能. 这个函数的特殊之处在于 ...
- python中的装饰器(以及多个装饰器详细执行过程)
装饰器 1.如果要增强一个函数的功能,但又不希望更改原函数中的代码,这种在代码运行期间动态增加功能的机制被称为装饰器 [Decorator] 2. 本质:实际上就是一个闭包,只不过被装饰的函数需要 ...
- python 装饰器分类_Python 装饰器(Decorators) 超详细分类实例
Python装饰器分类 Python 装饰器函数: 是指装饰器本身是函数风格的实现; 函数装饰器: 是指被装饰的目标对象是函数;(目标对象); 装饰器类 : 是指装饰器本身是类风格的实现; 类装饰器 ...
- python装饰器参数讲解_python装饰器的详细解析
写在前面: python装饰器(fuctional decorators)就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名(或类名)的情况下,给函数增加新的功能. 这个函数的特殊之处在于它的 ...
- python装饰器详解-python装饰器的详细解析
什么是装饰器? python装饰器(fuctional decorators)就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名(或类名)的情况下,给函数增加新的功能. 这个函数的特殊之处在于 ...
- Python|装饰器|执行时间|递归|动态属性|静态方法和类|继承和多态|isinstance类型判断|溢出|“魔法”方法|语言基础50课:学习记录(6)-函数的高级应用、面向对象编程、进阶及应用
文章目录 系列目录 原项目地址: 第16课:函数的高级应用 装饰器(记录执行时间的例子) 递归调用 简单的总结 第17课:面向对象编程入门 类和对象 定义类 创建和使用对象 初始化方法 打印对象 面向 ...
- python装饰器原理-看完这篇文章还不懂Python装饰器?
原标题:看完这篇文章还不懂Python装饰器? 1.必备 2.需求来了 初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作.redis调用.监控API等功能.业务部门 ...
- python装饰器原理-Python装饰器原理与用法分析
这篇文章主要介绍了Python装饰器原理与用法,结合实例形式分析了Python装饰器的概念.原理.使用方法及相关操作注意事项,需要的朋友可以参考下 本文实例讲述了Python装饰器原理与用法.分享给大 ...
最新文章
- 虚幻引擎虚拟现实开发基础学习教程
- 41 JavaScript中的闭包
- R语言使用ggplot2包使用geom_boxplot函数绘制基础分组箱图(输出多个分组、自定义颜色配置)实战
- 独家 | Tableau中的Z-Order了解一下!
- datanode 不能连接master
- linux rpm包,安装路径查看及改变rpm包默认安装路径
- Shiro学习总结(4)——Shrio登陆验证实例详细解读
- Java集合总结大全--史上最强
- 高可用(HA)集群原理概述
- 每日算法系列【LeetCode 658】找到 K 个最接近的元素
- python语法学习第十天--类与对象相关的BIF、魔法方法
- php怎么安装模板_php 模板框架之smarty 的下载和安装
- Android 百度地图定位
- 项目整体管理:结束项目或阶段
- linux引导文件制作U盘,Linux下制作U盘系统启动盘的方法
- 软件测试工程师职称评定细则
- 亚马逊森林大火----- 我也来评论一下
- 行走在数据库上的行癫(三)
- Ubuntu 20.04.3 “have unmet dependencies“
- 如何查询一个 app 的 Android 和 iOS 下载量?
热门文章
- Raft算法的Leader选举和日志复制过程
- 数据仓库与联机分析处理技术
- leetcode-- 338. Counting Bits
- leetcode--5. 最长回文子串
- Vue指令篇_v-for_列表渲染
- 1050 String Subtraction (20 分)_10行代码AC
- python找指定内容_python查找指定具有相同内容文件的方法
- linux网络设置与基础服务命令(ifconfig、hostname、route、netstat、ss、ping、traceroute、nslookup、route)
- java枚举新特性_java回顾之枚举和新特性
- next数组_【阿里面试热身题】数组去重(动画展示)