文章目录

  • 函数装饰器
    • 基本函数装饰器
    • 传参函数装饰器
  • 类装饰器
    • 基本类装饰器
    • 传参类装饰器
  • 装饰器执行顺序
  • 内置装饰器
    • @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++,根本不存在函数嵌套,哈哈~~接下来来仔细分析一下:

  1. 首先我们要清楚相比第一个例子,我们多加了一层函数嵌套,且这一层函数嵌套就是也仅仅是为了给装饰器传递参数,那么不难想到add_number2.__name__应该是inner_wrapper(不信你可以输出看一看);
  2. 基于第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装饰器详细剖析相关推荐

  1. Python装饰器详解,详细介绍它的应用场景

    装饰器的应用场景 附加功能 数据的清理或添加: 函数参数类型验证 @require_ints 类似请求前拦截 数据格式转换 将函数返回字典改为 JSON/YAML 类似响应后篡改 为函数提供额外的数据 ...

  2. python 装饰器 参数-python装饰器的详细解析

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

  3. python中的装饰器(以及多个装饰器详细执行过程)

    装饰器 1.如果要增强一个函数的功能,但又不希望更改原函数中的代码,这种在代码运行期间动态增加功能的机制被称为装饰器   [Decorator] 2. 本质:实际上就是一个闭包,只不过被装饰的函数需要 ...

  4. python 装饰器分类_Python 装饰器(Decorators) 超详细分类实例

    Python装饰器分类 Python 装饰器函数: 是指装饰器本身是函数风格的实现; 函数装饰器: 是指被装饰的目标对象是函数;(目标对象); 装饰器类 : 是指装饰器本身是类风格的实现; 类装饰器 ...

  5. python装饰器参数讲解_python装饰器的详细解析

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

  6. python装饰器详解-python装饰器的详细解析

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

  7. Python|装饰器|执行时间|递归|动态属性|静态方法和类|继承和多态|isinstance类型判断|溢出|“魔法”方法|语言基础50课:学习记录(6)-函数的高级应用、面向对象编程、进阶及应用

    文章目录 系列目录 原项目地址: 第16课:函数的高级应用 装饰器(记录执行时间的例子) 递归调用 简单的总结 第17课:面向对象编程入门 类和对象 定义类 创建和使用对象 初始化方法 打印对象 面向 ...

  8. python装饰器原理-看完这篇文章还不懂Python装饰器?

    原标题:看完这篇文章还不懂Python装饰器? 1.必备 2.需求来了 初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作.redis调用.监控API等功能.业务部门 ...

  9. python装饰器原理-Python装饰器原理与用法分析

    这篇文章主要介绍了Python装饰器原理与用法,结合实例形式分析了Python装饰器的概念.原理.使用方法及相关操作注意事项,需要的朋友可以参考下 本文实例讲述了Python装饰器原理与用法.分享给大 ...

最新文章

  1. 虚幻引擎虚拟现实开发基础学习教程
  2. 41 JavaScript中的闭包
  3. R语言使用ggplot2包使用geom_boxplot函数绘制基础分组箱图(输出多个分组、自定义颜色配置)实战
  4. 独家 | Tableau中的Z-Order了解一下!
  5. datanode 不能连接master
  6. linux rpm包,安装路径查看及改变rpm包默认安装路径
  7. Shiro学习总结(4)——Shrio登陆验证实例详细解读
  8. Java集合总结大全--史上最强
  9. 高可用(HA)集群原理概述
  10. 每日算法系列【LeetCode 658】找到 K 个最接近的元素
  11. python语法学习第十天--类与对象相关的BIF、魔法方法
  12. php怎么安装模板_php 模板框架之smarty 的下载和安装
  13. Android 百度地图定位
  14. 项目整体管理:结束项目或阶段
  15. linux引导文件制作U盘,Linux下制作U盘系统启动盘的方法
  16. 软件测试工程师职称评定细则
  17. 亚马逊森林大火----- 我也来评论一下
  18. 行走在数据库上的行癫(三)
  19. Ubuntu 20.04.3 “have unmet dependencies“
  20. 如何查询一个 app 的 Android 和 iOS 下载量?

热门文章

  1. Raft算法的Leader选举和日志复制过程
  2. 数据仓库与联机分析处理技术
  3. leetcode-- 338. Counting Bits
  4. leetcode--5. 最长回文子串
  5. Vue指令篇_v-for_列表渲染
  6. 1050 String Subtraction (20 分)_10行代码AC
  7. python找指定内容_python查找指定具有相同内容文件的方法
  8. linux网络设置与基础服务命令(ifconfig、hostname、route、netstat、ss、ping、traceroute、nslookup、route)
  9. java枚举新特性_java回顾之枚举和新特性
  10. next数组_【阿里面试热身题】数组去重(动画展示)