zoukankan      html  css  js  c++  java
  • 装饰器

    1. 函数装饰器

       装饰器(fuctional decorators)可以定义成函数,来拓展原函数的功能,这个函数的特殊之处在于它的返回值也是一个函数,这个函数是内嵌“原”函数的函数。

       本质上函数装饰器就是一个返回函数的高阶函数。函数装饰器被装饰函数定义好后立即执行。

       1)最原始的装饰器

          结合下面的例子。被装饰函数 f 即使没有被调用,但因为被 @deco 修饰,所以函数装饰器的代码已经运行了,即 deco 已经运行,deco内部嵌套的 wrapper

          显然还没有运行,只是被定义了下,然后返回 wrapper 函数名变量执行流程如下:

          a. 先定义原函数 f。

          b.  然后运行函数装饰器,即 f = deco(f)。

             我们看到装饰器语法:@函数装饰器名,函数后面加上()才表示调用执行,故这个调用由解释器完成,它会自动执

             行语句:deco(f),即以被装饰函数为参数来调用函数装饰器。如果我们这么修饰呢:@函数装饰器名()即用 @deco() 代替 @deco,

             那么自动调用机制会执行:deco()(f),显然就会出错,除非让deco()返回一个带参数 func 的函数。

             函数装饰器执行完后,被装饰函数名的引用就变了,变成函数装饰器的返回值 wrapper,即 f = deco(f) = wrapper

    def deco(f):
        def wrapper():
            print("decorate begin")
            f()
            print("decorate end")
        return wrapper
    
    @deco  # 此行代码等同于,f = deco(f) = wrapper
    def f():
        print("call f")
    
    f()  # f = deco(f) = wrapper => f() = wrapper()
    
    """
    decorate begin
    call f
    decorate end
    """

       2)带有固定参数的装饰器

          由1)中的解释,我们可以知道调用f时,实际调用的是wrapper,参数先通过wrapper的形参传入,然后wrapper内部再调用函数f,参数再间接传给f。

          这种情况下,函数 f 和 wrapper 的参数个数和形式必须是一致的。

    def deco(f):
        def wrapper(a, b):
            print("decorate begin")
            f(a, b)
            print("decorate end")
        return wrapper
    
    @deco
    def f(a, b):
        print("call f")
    
    f(3, 4)  # f = deco(f) = wrapper => f(3,4) = wrapper(3,4)

          因为类的实例方法固定都会有一个参数,即self,所以如果将函数装饰器修饰类方法,那么作为装饰器嵌套的函数至少得带有一个参数。举个例子

    def deco(f):
        def wrapper(obj, s):   # 第一个参数是实例对象
            print("decorate begin")
            f(obj, s)
            print("decorate end")
        return wrapper
    
    class Bar:
        @deco
        def f(self, s):     # 这里有两个参数,所以wrapper也得有相同的参数形式
            print("call f")
            print(s)
            print("call f end")
    
    x = Bar()
    x.f("hello world")    # 因为 f 是 x 的方法,所以会隐式传递 self 参数,x.f("hello world") => f(x, "hello world") => wrapper(x, "hello world")

       3)带有可变参数的装饰器

          如果可变参数语法不理解请先阅读另一篇博客 默认参数和可变参数

    def deco(f):
        def wrapper(*args, **kwargs):  # args = (3,4), kwargs = {}
            print("decorate begin")
            f(*args, **kwargs)         # args 和 kwargs 分别被解包后,再整体按顺序赋值给 f 的形参
            print("decorate end")
        return wrapper
    
    @deco
    def f(a, b):
        print("call f")
    
    f(3, 4)

       4)使用多个装饰器,装饰一个函数

          多个装饰器函数装饰的顺序是从里到外,也可以说由近到远,直接来看一个例子。

    def deco01(f):
        def wrapper(*args, **kwargs):
            print("this is deco01")
            f(*args, **kwargs)
            print("deco01 end here")
        return wrapper
    
    def deco02(f):
        def wrapper(*args, **kwargs):
            print("this is deco02")
            f(*args, **kwargs)
            print("deco02 end here")
        return wrapper
    
    @deco01
    @deco02
    def f(a,b):
        print("我是被装饰的函数")
    
    f(3,4)
    
    """
    output:
    this is deco01
    this is deco02
    我是被装饰的函数
    deco02 end here
    deco01 end here
    """
    

       按由近到远的原则,首先先装饰deco02,便得到下面的函数体:

    print("this is deco02")
    f(*args, **kwargs)
    print("deco02 end here")

       然后继续装饰deco01,在已经装饰了deco02的基础上,继续扩展代码,函数体就变成这样:

    print("this is deco01")
    print("this is deco02")
    f(*args, **kwargs)
    print("deco02 end here")
    print("deco01 end here")
    

       给个图,一目了然:

       

    2. 类装饰器

       代码逻辑很复杂时,不适合写在一个函数内,这时就可以使用类来实现。由函数装饰器可以知道,装饰器的执行其实就是:f = deco(f)。

       把装饰器定义成类也是一样的,它与函数装饰器的区别如下:

          1)deco 由函数名变成类名。

          2)deco(f)含义不同,原先表示函数的执行,现在表示类实例化,即对象的定义。

          3)函数装饰器执行完成后,f 重新引用的是装饰器的返回值; 而类装饰器实例化后,f 就引用了该实例。

       所以这个类对象必须是可调用的,即f()能执行,就像 C++ 语法中的函数对象,即重载()运算符。python 中是通过实现__call__方法来达到这个目的。

       实例变为可调用对象,与普通函数一致,执行的逻辑是 __call__ 函数内部逻辑。

    class Bar:
        def __call__(self, *args, **kwargs):
            print(*args, **kwargs)
    
    b = Bar()  # 实例化
    b("I am instance method.")   # 等价于:b.__call__("I am instance method.")

       类装饰器 __call__ 方法内的逻辑相当于函数装饰器内嵌的 wrapper 函数。举个例子,用类装饰器装饰普通函数:

    class Deco:
        def __init__(self, f):
            self.func = f
    
        def __call__(self):
            print("Start __call__")
            self.func()
            print("End __call__ ")
    
    @Deco           # 函数定义完后会执行:hello = Deco(hello),hello 不再是函数引用,而是装饰器类对象
    def hello():   
        print("Hello")
    
    hello()   # 其实是装饰器类对象的执行,hello() => Deco(hello)()
    
    """
    Start __call__
    Hello
    End __call__ 
    """
    

       如果把类装饰器用来装饰其它的类成员函数呢?参考用函数装饰器装饰类方法,装饰器内嵌的那个函数至少得存在一个参数来提供实例,因为类装饰器的

       执行最终是调用 __call__ 函数,所以 __call__ 函数至少得存在两个参数,一个是 self,另一个提供给被装饰函数的 self。

    class Deco:
        def __init__(self, f):
            self.func = f
    
        def __call__(self, own):    # 将 Test 实例对象传入,提供给 f
            print("Start __call__")
            self.func(own)
            print("End __call__")
    
    class Test:
        @Deco          # f = Deco(f)
        def f(self):   # 被装饰函数 f 有一个 self 参数
            print("hello")
    
    t = Test()
    t.f(t)     # t.f 被装饰后就是一个 object 而不是 method,所以没有传递 self 参数,t.f(t) = f(t) = Deco(f)(t) = __call__(Deco(f), t)
    
    """
    Start __call__
    hello
    End __call__
    """
    

       如果不想显示地传递这个实例参数,即 t.f(t) 改成 t.f(),该怎么做呢?可以实现一个__get__方法,即一个描述器,相关语法请先阅读:描述器

    from types import MethodType
    
    class Deco:
        def __init__(self, f):
            self.func = f
    
        def __call__(self, own):
            print("Start __call__")
            self.func(own)
            print("End __call__")
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            else:
                return MethodType(self, instance) # Deco 对象是可调用的,可以将它当作方法绑定 Test 对象
    
    class Test:
        @Deco     # f = Deco(f)
        def f(self):
            print("hello")
    
    t = Test()
    t.f()   # 因为 Deco 对象通过 MethodType 绑定到 Test 实例上了, 所以 f 此时是一个method,需要先传 self 参数
            # 我们可以推知:t.f() => f(t) => Deco(f)(t) => __ call__(Deco(f), t)

       __call__ 可以使用可变类型参数,增加普适性:

    from types import MethodType
    
    class Deco:
        def __init__(self, f):
            self.func = f
    
        def __call__(self, *args, **kwargs):
            print("Start __call__")
            self.func(*args, **kwargs)
            print("End __call__")
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            else:
                return MethodType(self, instance)
    
    class Test:
        @Deco
        def f(self):
            print("hello")
    
    t = Test()
    t.f()
    

      

    3. python内置的函数装饰器

       有3种,分别是 @staticmethod、@classmethod 和 @property。

       1)@staticmethod 修饰的方法是类静态方法:其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用,也可以在实例化的情况使用。

          由于静态方法不包含 self 参数,所以它只能通过类名访问类成员,且只能访问静态成员,如 类名.属性名、类名.方法名。

    class Test(object):
        x = 0.1
        @staticmethod
        def f():
            print("call static method.")
            Test.x = 0.5
    
    Test.f()       # 静态方法无需实例化
    Test().f()     # 也可以实例化后调用
    print(Test.x)
    
    """
    output:
    call static method.
    call static method.
    0.5
    """   

       2)@classmethod 修饰的方法是类方法:与实例方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型,有子类继承时,

          就是子类类型)。类方法可以在类不进行实例化的情况下调用,也可以在实例化的情况使用。但静态方法的行为就是一个普通的全局函数,而类方法包含cls参

          数,那 cls 参数有啥用呢?

          解释:比如静态方法想要调用非静态的成员,必须知道类的具体类型,如在函数内部构造一个实例来调用,在存在派生类的代码中,知道具体类型还挺麻烦,

                如果类名被修改,那代码就也得改。但 classmethod 方法却可以直接知道类的具体类型,即通过 cls 参数。看一个例子便清楚了:

    class Test(object):
        a = 123
        def normalFoo(self):
            print('call normalFoo.')
    
        @staticmethod
        def staticFoo():
            print('call staticFoo.')
            print(Test.a)
            Test().normalFoo()  # 访问非静态成员
    
        @classmethod
        def classFoo(cls):
            print('call classFoo.')
            print(Test.a)
            cls().normalFoo()   # 访问非静态成员
    
    Test.staticFoo()
    Test.classFoo()
    

        3)property(把函数变属性):把一个方法变成属性调用,起到既能检查属性,还能用属性的方式来访问该属性。

          访问属性的时候不需要是可调用的(即不用在后面加括号),所以装饰器没必要实现 __call__ 方法,它其实就是一个地地道道的描述器。

          为什么需要它呢?我们在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把属性随便改:

    class Student(object):
        def __init__(self, score = 0):
            self.score = score
    
    s = Student()
    s.score = 100
    s.score = 200  # 分数为200明显不合理
    s.score = -50  # 分数为负数也不合理
    print(s.score)
    

          对值进行约束的一个明显解决方案是隐藏属性 score(使其私有)并定义新的 getter 和 setter 接口来操作它。可以按照下面这样改,但这样

          做存在的一个大问题:所有在其程序中实现我们前面的类的客户都必须修改他们的代码,将 s.score 修改为 s.getScore(),并且将像 s.score = val

          所有赋值语句修改为 s.setScore(val)。这种重构可能会给客户带来数十多万行代码的麻烦。

    class Student(object):
        def __init__(self, value=0):
            self.setScore(value)
    
        def getScore(self):
            return self._score
    
        def setScore(self, value):
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    
    s = Student()
    s.setScore(100)
    s.setScore(105)     # 报错
    s.setScore(-50)     # 报错
    print(s.getScore())
    

          这时 property 就派上用场了。@property 真正强大的就是可以对属性增加约束来限制属性的定义。

    class Student(object):
        def __init__(self, value=0):
            self._score = value
    
        @property        # 以需要定义的属性为方法名,如果没有@属性名.setter,则就是一个只读属性
        def score(self):
            return self._score
    
        @score.setter    # @property定义可访问属性的语法:以属性名为方法名,并在方法名上增加@属性名.setter
        def score(self, value):  
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    
    s = Student()
    s.score = 100    #
    s.score = 105    # 报错
    s.score = -50    # 报错
    print(s.score)
    

      

  • 相关阅读:
    #一周五# (视频) 手掌四轴Estes 4606,树莓派2和WRTNode,WinHEC 2015深圳
    Android 自定义标题栏
    (视频)《快速创建网站》 4.1 为啥造软件不同于造汽车,为啥是软件就一定会有Bug - 构建开发运维一体化(DevOps)
    (视频) 《快速创建网站》3.4 网站改版3分钟搞定 - WordPress主题安装和备份
    OpenCV由汉字生成图片(透明)----可以对抗论文查重!!!
    Codeforces Round #295 (Div. 2)
    Codeforces Round #294 (Div. 2)
    Codeforces Round #293 (Div. 2)
    Codeforces Round #292 (Div. 2)
    暴力/set Codeforces Round #291 (Div. 2) C. Watto and Mechanism
  • 原文地址:https://www.cnblogs.com/yanghh/p/13156575.html
Copyright © 2011-2022 走看看