python3进阶篇(二)——深析函数装饰器

前言:
阅读这篇文章我能学到什么?
  装饰器可以算python3中一个较难理解的概念了,这篇文章由浅入深带你理解函数装饰器,请阅读它。

——如果您觉得这是一篇不错的博文,希望你能给一个小小的赞,感谢您的支持。

目录

  • python3进阶篇(二)——深析函数装饰器
    • 1 装饰器基本概念
    • 2 创建装饰器
      • 2.1 创建装饰器并且不使用@符号
      • 2.2 一种装饰器给多个函数进行装饰
    • 3 装饰器@符号的使用
      • 3.1 使用@符号装饰函数
      • 3.2 使用@符号让一个装饰器装饰多个函数
      • 3.3 被装饰函数的__name__和__doc__参数
    • 4 带参的装饰器
      • 4.1 创建带参装饰器并且不使用@符号
      • 4.2 使用@符号创建带参装饰器
    • 5 装饰器类
    • 6 多层装饰

1 装饰器基本概念

  装饰器是能够修改已定义函数功能的函数,也即装饰器本身就是具有这种特殊功能的 函数 。修改是有限制的修改,它只能在你定义的函数执行前或执行后执行其他代码,当然也能给你的函数传递参数。不能直接修改你定义的函数内部的代码。
  举个通俗的例子。比如你定义了函数A,那么函数A被装饰器装饰之后,此时你调用A,它可能先去执行一些动作(代码)然后执行函数A,完了又去执行另外一些动作(代码)。也即装饰器帮你的函数附加了一些动作(代码)执行,它改变了你原来定义的函数功能。
  如果你看过我的上一篇进阶篇关于函数的讲解(其中降到了函数嵌套定义、函数作为参数、函数返回函数等问题),那么后续的内容将会更容易理解。

2 创建装饰器

2.1 创建装饰器并且不使用@符号

  先不考虑使用@符号。我们从装饰器的功能出发,尝试自己去实现这样功能的函数,这样有助于我们理解装饰器的作用。
代码示例:

def Decorator(func):                            #装饰器,对函数进行装饰print("Call Decorator")def Func():print("Call Func")print("Before do something")func()print("After do something")return Func                                 #返回内部嵌套定义的函数地址def Function():                                 #功能函数print("Call Function")Function()                                      #装饰前调用
print(Function)                                 #引用指向函数Function
print("------------------------------------")
Function = Decorator(Function)                  #对功能函数进行装饰
print("------------------------------------")
print(Function)                                 #引用指向函数Func
Function()

运行结果:

Call Function
<function Function at 0x0000026E332D04C0>
------------------------------------
Call Decorator
------------------------------------
<function Decorator.<locals>.Func at 0x0000026E332D0550>
Call Func
Before do something
Call Function
After do something

  Function是我们的功能函数,我们按照自己的需求进行函数定义,它的功能是明确的,在装饰器“装饰”前我们进行调用,它的功能确定是输出字符串Call Function。我们此时输出函数地址为0x0000026E332D04C0。函数Decorator很特殊,它以函数作为参数func。内部嵌套定义了一个函数Func,在这个函数里调用通过参数传递进来的外部函数,需要注意的是内部函数Func在外部函数func调用的前后都添加了自己的代码逻辑,没错,这部分就是装饰器装饰上的内容。最后Decorator函数返回的是内部函数Func的函数地址即0x0000026E332D0550。其实Decorator就是装饰器。
  仔细一想,Decorator这个函数非常有意思,输入外部函数,在其内部函数中调用执行外部函数,并且在这个外部函数前后都添加上附加的代码,然后返回内部函数的地址方便外部调用这个内部函数。
  注意Function = Decorator(Function)这里是将函数地址作为参数传入,然后返回的函数地址赋值给变量,也即引用Function一开始指向的是Function函数,得装饰器返回值后指向的是装饰器内的函数Func

2.2 一种装饰器给多个函数进行装饰

  装饰器创建好后可以对外部函数进行装饰,对被装饰的函数并没有进行限制。所以我们可以对多个完全不同的函数使用同一个装饰器对其装饰。
代码示例:

def Decorator(func):                            #装饰器,对函数进行装饰print("Call Decorator")def Func():print("Call Func")print("Before do something")func()print("After do something")return Func                                 #返回内部嵌套定义的函数地址def Function1():                                #功能函数print("Call Function1")def Function2():                                #功能函数print("Call Function2")Function1 = Decorator(Function1)                #对功能函数进行装饰
print("---------------------------")
Function2 = Decorator(Function2)                #对功能函数进行装饰
print("---------------------------")Function1()                                     #装饰后调用
print("---------------------------")
Function2()                                     #装饰后调用

运行结果:

Call Decorator
---------------------------
Call Decorator
---------------------------
Call Func
Before do something
Call Function1
After do something
---------------------------
Call Func
Before do something
Call Function2
After do something

  同一个装饰器对不同的Function1Function2函数进行了同一种“装饰”。

3 装饰器@符号的使用

3.1 使用@符号装饰函数

  @+装饰器名然后跟函数定义,表明定义的函数被指定的装饰器进行装饰。我们回顾一下上面没有使用@符号的时候是怎么对Function函数进行装饰的。首先我们定义了装饰器函数Decorator,然后通过调用这个装饰器函数即Function = Decorator(Function),这句指明了Function函数被装饰器Decorator修饰,并且最后引用变量还是使用Function。也就是说功能函数Function的定义和它被装饰器“装饰”是分开的。Function定义后装饰前,我们还能调用到没有被装饰过的Function函数。使用@符号进行装饰的作用在于函数完成定义后就立即被指定的装饰器装饰(你没法再调用到没有被装饰时的状态),另外就是写法上简洁统一(函数定义和装饰在一个位置)。
代码示例:

def Decorator(func):                          #装饰器,对函数进行装饰print("Call Decorator")def Func():print("Call Func")print("Before do something")func()print("After do something")return Func@Decorator                                    #使用装饰器对函数Function进行装饰,函数完成定义后就立刻进行了装饰
def Function():print("Call Function")print("----------------------")
Function()                                    #装饰后调用函数

运行结果:

Call Decorator
----------------------
Call Func
Before do something
Call Function
After do something

  在不使用@进行函数装饰时,在装饰前我们依旧可以调用到没有经过“装饰”的Function函数,而使用@在函数定义处完成了装饰。不管使用哪种方法,我们都需要定义好装饰器函数。

3.2 使用@符号让一个装饰器装饰多个函数

  同样的,使用@符号在几个不同函数的定义处都可以指明被同一个装饰器装饰(装饰器是可以复用的)。
代码示例:

def Decorator(func):                          #装饰器,对函数进行装饰print("Call Decorator")def Func():print("Call Func")print("Before do something")func()print("After do something")return Func@Decorator
def Function1():print("Call Function1")@Decorator
def Function2():print("Call Function2")print("------------------------------------")
Function1()
print("------------------------------------")
Function2()

运行结果:

Call Decorator
Call Decorator
------------------------------------
Call Func
Before do something
Call Function1
After do something
------------------------------------
Call Func
Before do something
Call Function2
After do something

  以上你已经学会创建一个真正的装饰器并使用它。但还有些美中不足的地方。

3.3 被装饰函数的__name__和__doc__参数

  python3的函数有一些内置参数,比如__name__存储函数名(在定义时决定),__doc__用于保存函数的描述信息(这个在基础篇函数章节有讲过)。
代码示例:

def add(a, b):"Get the sum of two numbers"              #函数的描述信息return a + bFun = addprint(add.__name__)
print(add.__doc__)
print("----------------------------")
del add
print(Fun.__name__)                           #函数名已经是add
#print(add.__name__)                          #error: NameError: name 'add' is not defined

运行结果:

add
Get the sum of two numbers
----------------------------
add

  可以看到,即使给函数创建新的引用,用新的应用输出函数名print(Fun.__name__),函数名也依旧是add,也即函数名在函数定义时确定,和引用变量名无关。我们将最初的引用add进行删除del add,此时不能继续通过add访问函数的属性,但是可以继续通过其他引用访问。
  说了这么多就是想告诉你,函数定义时就决定了一些函数的属性信息,这些信息会保存在python内置的函数属性里。装饰器是将一个外部函数输入,输出自己的内部函数,然后引用变量名不变,但实际指向的已经不是原来那个函数了。会存在一个问题,定义的功能函数经过装饰器后,函数的属性信息都变了,变成内部函数的。python还有很多其他的函数内置属性,这里我们只以__name____doc__属性举例。
代码示例:

def Decorator(func):                          #装饰器,对函数进行装饰"Description: Decorator"def Func():"Description: Func"print("Before do something")func()print("After do something")return Func@Decorator
def Function():"Description: Function"print("Call Function1")print(Function.__name__)
print(Function.__doc__)

运行结果:

Func
Description: Func

  输出的并不是Function定义时的信息,而是内部嵌套函数Func的信息。所以为了追求这一点完美,我们需要进行一些优化。
  python为我们提供了满足这种需求的方法,即将函数的属性信息替换为原来的。
代码示例:

from functools import wrapsdef Decorator(func):                          #装饰器,对函数进行装饰"Description: Decorator"@wraps(func)def Func():"Description: Func"print("Before do something")func()print("After do something")return Func@Decorator
def Function():"Description: Function"print("Call Function1")print(Function.__name__)
print(Function.__doc__)

运行结果:

Function
Description: Function

  我们导入了wraps模块,并且将内部函数Func用装饰器wraps进行了装饰,进过这一次装饰后将函数的属性信息替换回了原来的。注意下,这里的装饰器wraps是带参数的,传递进外部函数地址。后面我们继续讲带参数的装饰器。
  好了,现在你已经大致理解了装饰器的概念和作用了,我们继续看更深入的用法。

4 带参的装饰器

  如果看过我上一篇进阶篇关于函数的深入讲解就很容易能理解装饰器怎么实现带参数的。回顾一下上面不带参数的构造器,我们对装饰器函数内进行了嵌套定义,也即它是两层函数,如果我们继续在外面嵌套上一层,利用外面这层函数的参数列表就能实现装饰器的带参了。当然它的返回值也必须是内层函数的地址,这样才能实现递推式的调用。

4.1 创建带参装饰器并且不使用@符号

  同样的,我们先不适用@进行带参装饰器创建,这样有利于我们理解@符号到底做了什么动作。
代码示例:

from functools import wrapsdef Operation(Parameter):                                               #这里的参数列表就是装饰器的参数列表def OperationDecorator(OutFunc):                                    #装饰器,对函数进行装饰@wraps(OutFunc)def InFunc(Num1, Num2):                                         #含有外部函数的参数列表print("Before do something")print(Parameter)Ret = OutFunc(Num1, Num2)                                   #调用外部函数print("After do something")return Ret                                                  #内部函数返回值就是函数调用最终的返回值return InFuncreturn OperationDecoratordef add(Num1, Num2):return Num1 + Num2add = Operation("Calculate the sum of two numbers")(add)                #带参数装饰器print(add(1, 2))

运行结果:

Before do something
Calculate the sum of two numbers
After do something
3

  相比于之前不带参数的装饰器,我们在多嵌套了一层函数Operation,这个函数具有参数,并且其返回值为内层函数OperationDecorator,这层函数的作用就是我们上面讲过的将外部函数地址替换为内部函数地址。所以就不难理解add = Operation("Calculate the sum of two numbers")(add)这行代码的作用了,先调用Operation函数并给其传入参数,该函数返回OperationDecorator函数地址,此时函数地址+(add)就又构成了函数调用,此时就和不带参装饰器用法相同了,add引用最终指向了内部函数InFunc,在内部函数里可以拿到我们给装饰器传入的参数Parameter(内层函数可以访问外层函数的变量)。

4.2 使用@符号创建带参装饰器

  @符号的作用不变,让创建迭代器写法更简洁,让函数在的定义和装饰在一处。
代码示例:

from functools import wrapsdef Operation(Parameter):                                           #这里的参数列表就是装饰器的参数列表def OperationDecorator(OutFunc):                                #装饰器,对函数进行装饰@wraps(OutFunc)def InFunc(Num1, Num2):                                     #含有外部函数的参数列表print("Before do something")print(Parameter)Ret = OutFunc(Num1, Num2)                               #调用外部函数print("After do something")return Ret                                              #内部函数返回值就是函数调用最终的返回值return InFuncreturn OperationDecorator@Operation("Calculate the sum of two numbers")                      #带参数装饰器
def add(Num1, Num2):return Num1 + Num2print(add(1, 2))

运行结果:

Before do something
Calculate the sum of two numbers
After do something
3

  这就是带参装饰器了,需要注意一点。带参也可以是无参数的,什么意思?就是说嵌套了三层函数,最外层的函数也可以是无参,但是此时无参也不能省略()符号。
代码示例:

from functools import wrapsdef Operation():                                                    #这里的参数列表就是装饰器的参数列表def OperationDecorator(OutFunc):                                #装饰器,对函数进行装饰@wraps(OutFunc)def InFunc(Num1, Num2):                                     #含有外部函数的参数列表print("Before do something")Ret = OutFunc(Num1, Num2)                               #调用外部函数print("After do something")return Ret                                              #内部函数返回值就是函数调用最终的返回值return InFuncreturn OperationDecorator@Operation()                                                        #带参数装饰器
def add(Num1, Num2):return Num1 + Num2print(add(1, 2))

运行结果:

Before do something
After do something
3

  这里的@Operation()不能写作@Operation,即使它没有参数,因为这种形式是嵌套了三层函数。

5 装饰器类

  为了更好的封装性,我们可以将装饰器封装为一个类,但是通过@符号的用法确保持不变。所谓装饰器类就是把装饰器定义在类中。用到了类的两个方法。一个是类的构造函数__init__,构造函数的参数列表就是装饰器的参数列表,带参情况取决于构造函数。另一个是__call__方法,该方法使得实例化的对象可以被直接访问,通俗点说正常情况下访问类的方法是通过对象名+.``方法名,而直接访问对象就为对象名+()参数列表,直接访问时执行的就是__call__方法。另外还需要注意的是,使用装饰器类装饰函数,在函数定义并装饰时就调用了__init____call__方法。
代码示例:

from functools import wrapsclass cDecorator:def __init__(self, Parameter):                      #构造函数的参数列表就是装饰器的传输列表print("Call __init__")self.Parameter = Parameterpassdef __call__(self, func):                           #call函数将实例化的对象可直接调用print("Call __call__")@wraps(func)def Func(Num1, Num2):print("Call Func")print(self.Parameter)                       #访问成员变量,值为装饰器输入参数self.FuncA()                                #访问成员函数return func(Num1, Num2)return Funcdef FuncA(self):print("call FuncA")pass@cDecorator("A")                                        #带参装饰器
def add(Num1, Num2):print("Call add")return Num1 + Num2print("----------------------------")
print(add(1, 2))

运行结果:

Call __init__
Call __call__
----------------------------
Call Func
A
call FuncA
Call add
3

  需要留意这些函数的执行顺序以及什么时候被执行(是在装饰时执行还是在被装饰函数调用时执行)。

6 多层装饰

  一个函数可以反复被多次装饰,即对装饰过得函数再进行装饰。我们可以使用不同的装饰器去装饰一个函数,也可以用同一个装饰器重复装饰器一个函数,但原理都是一样的,下面我们用前面带参数装饰器的例子,对同一个函数用同一个装饰器装饰两次来说明这种方法。
代码示例:

from functools import wrapsdef Operation(Parameter):                                           #这里的参数列表就是装饰器的参数列表print("Call Operation", Parameter)def OperationDecorator(OutFunc):                                #装饰器,对函数进行装饰print("Call Operation_Decorator", Parameter)@wraps(OutFunc)def InFunc(Num1, Num2):                                     #含有外部函数的参数列表print("Before do something", Parameter)print(Parameter)Ret = OutFunc(Num1, Num2)                               #调用外部函数print("After do something", Parameter)return Ret                                              #内部函数返回值就是函数调用最终的返回值return InFuncreturn OperationDecorator@Operation("A")                      #带参数装饰器
@Operation("B")                      #带参数装饰器
def add(Num1, Num2):return Num1 + Num2print("---------------------------------")
print(add(1, 2))

运行结果:

Call Operation A
Call Operation B
Call Operation_Decorator B
Call Operation_Decorator A
---------------------------------
Before do something A
A
Before do something B
B
After do something B
After do something A
3

  相信从运行结果你已经看出规律来了,这就跟拿袋子套东西一个道理,我们先给功能函数套上一层B,然后在套上一层A。假设功能函数的执行我们记为F,当我们给他套上一层B时,功能函数执行前后就多了一层B的装饰,即 BFB,然后继续套上A变为 ABFBA,更多层的嵌套同理。不管套多少层中间最核心的F只会执行一次,函数只会有一个返回值。

python3进阶篇(二)——深析函数装饰器相关推荐

  1. Android日志[进阶篇]二-分析堆栈轨迹(调试和外部堆栈)

    Android日志[进阶篇]一-使用 Logcat 写入和查看日志 Android日志[进阶篇]二-分析堆栈轨迹(调试和外部堆栈) Android日志[进阶篇]三-Logcat命令行工具 Androi ...

  2. nas安装emby_威联通QNAP系统入门进阶 篇二:宅家新姿势—威联通NAS安装套件版Emby搭建家庭影音服务器...

    威联通QNAP系统入门&进阶 篇二:宅家新姿势-威联通NAS安装套件版Emby搭建家庭影音服务器 2020-02-04 19:38:54 123点赞 1466收藏 123评论 你是AMD Ye ...

  3. [python 进阶] 第7章 函数装饰器和闭包

    文章目录 7.1 装饰器基础知识 7.2 Python何时执行装饰器 7.3 使用装饰器改进"策略" 7.4 变量作用域(global) 备注 -比较字节码(暂略) 7.5 闭包 ...

  4. python四大高阶函数_详谈Python高阶函数与函数装饰器(推荐)

    一.上节回顾 Python2与Python3字符编码问题,不管你是初学者还是已经对Python的项目了如指掌了,都会犯一些编码上面的错误.我在这里简单归纳Python3和Python2各自的区别. 首 ...

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

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

  6. python装饰器函数-Python函数装饰器常见使用方法实例详解

    本文实例讲述了Python函数装饰器常见使用方法.分享给大家供大家参考,具体如下: 一.装饰器 首先,我们要了解到什么是开放封闭式原则? 软件一旦上线后,对修改源代码是封闭的,对功能的扩张是开放的,所 ...

  7. python进阶(小白也能看懂)——装饰器浅谈(一)

    python进阶(小白也能看懂)--装饰器(一) 第四篇 文章目录 python进阶(小白也能看懂)--装饰器(一) 1.函数基础知识 例子1.1 例子1.2 例子1.3 例子1.4 2.不带参数的装 ...

  8. python类方法调用装饰_Python3 @classmethod 函数装饰器 声明一个类方法

    Python3 @classmethod 函数装饰器 声明一个类方法 @classmethod函数装饰器的主要作用是将一个类的普通方法(需要实例化使用)声明为一个类方法(可以直接使用类名调用).在类的 ...

  9. python装饰器实例-Python函数装饰器--实例讲解

    一.装饰器定义: 1.装饰器的本质为函数: 2.装饰器是用来完成被修饰函数的附加功能的 所以:装饰器是用来完成被修饰函数附属功能的函数 装饰器的要求: 1.不能修改被修饰函数的源代码: 2.不能更改被 ...

最新文章

  1. Java学习笔记——封装
  2. 各种网络监控拓扑图,十分齐全!
  3. python之环境变量(测试环境可配置)(亲测)
  4. ironpython2.7.9_Microsoft IronPython_.NET和Python实现平台下载 v2.7.9.1000最新官方版
  5. win10电脑安装android,5步教你如何在Win10 PC上安装Android 10
  6. ublox Android 定位超时,[RK3288] [Android 7.1] u-blox GPS调试
  7. NSUserDefault的使用
  8. 我要做 Android 之面笔试
  9. “菁客”发布《2018中国移动社交招聘趋势报告》
  10. uchome 2.0 存在持久XSS漏洞
  11. [历年IT笔试题]美团2015校园招聘笔试题
  12. 高等数学几何图形凸优化
  13. Si523超低功耗带自动寻卡13.56MHz非接触式读写器 替代MFRC523
  14. dpdk加速网络协议栈ANS用户手册
  15. 鸿海成立AI研发中心 5年投资100亿新台币
  16. Git版本回退的两种方式及回退方式推荐
  17. Spring框架基础(2)----Bean的创建及标签属性
  18. JavaScript基本数据类型之String 和 Boolean
  19. 收集欢太积分可参与丰富的用户活动,还有丰厚的福利可以领取~
  20. 计算机回收站设置大小,电脑回收站无法调整容量的大小怎么办?

热门文章

  1. DA方法论之SCQA模型
  2. 讲讲简单的电源隔离和信号地,电源地的处理
  3. Apache Spark 3.0 SQL DataFrame和DataSet指南
  4. 清华非全日制计算机硕士,2021年清华大学硕士招生分析,专硕非全日制占比高达67%...
  5. steam教育文化传承的必要性
  6. Markdown添加目录(自定义目录)
  7. opporeno3详细参数_opporeno3pro参数配置详情-opporeno3pro手机性能评测
  8. 财会法规与职业道德【7】
  9. C++基础到实战开发(附带课程源码)
  10. 5G通信时代汽车智能座舱发展趋势探讨