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

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

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

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

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 + b

Fun = add

print(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 wraps

def 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 wraps

def 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 InFunc
    return OperationDecorator

def add(Num1, Num2):
    return Num1 + Num2

add = 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 wraps

def 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 InFunc
    return OperationDecorator


@Operation("Calculate the sum of two numbers")                      #带参数装饰器
def add(Num1, Num2):
    return Num1 + Num2

print(add(1, 2))

运行结果:

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

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

from functools import wraps

def 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 InFunc
    return OperationDecorator


@Operation()                                                        #带参数装饰器
def add(Num1, Num2):
    return Num1 + Num2

print(add(1, 2))

运行结果:

Before do something
After do something
3

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

5 装饰器类

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

from functools import wraps

class cDecorator:
    def __init__(self, Parameter):                      #构造函数的参数列表就是装饰器的传输列表
        print("Call __init__")
        self.Parameter = Parameter
        pass

    def __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 Func

    def FuncA(self):
        print("call FuncA")
        pass

@cDecorator("A")                                        #带参装饰器
def add(Num1, Num2):
    print("Call add")
    return Num1 + Num2

print("----------------------------")
print(add(1, 2))

运行结果:

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

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

6 多层装饰

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

from functools import wraps

def 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 InFunc
    return OperationDecorator


@Operation("A")                      #带参数装饰器
@Operation("B")                      #带参数装饰器
def add(Num1, Num2):
    return Num1 + Num2

print("---------------------------------")
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只会执行一次,函数只会有一个返回值。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值