• Python(三)对装饰器的理解


    • 装饰器是 Python 的一个重要部分,也是比较难理解和使用好的部分。下面对装饰器做一下简单整理

    1. 前言

    • 装饰器实际上是应用了设计模式里,装饰器模式的思想:
      • 在不概念原有结构的情况下,添加新的功能
      • 类似于我们穿不同的衣服,可以先穿一件衬衫,再穿一件毛衣,再穿一件羽绒服
      • 但是毛衣不会影响羽绒服,羽绒服也不会影响衬衫
      • 随时更换,同一个人可以有不同的穿衣打扮
    • 对比之下,每一个装饰器就代表上述的一件衣服,我们可以根据功能需求,给一个函数本身加上不同的外套,也可以调整外套之间的顺序
    • 装饰器本质上就是一个个的 Python 函数对象,不改动代码的前提下增加功能,返回值也为是一个函数对象
    • 主要用于有切面需求的场景:记录日志、打点监控、权限校验、事务处理等场景

    2. 函数

    • 理解装饰器首先要理解 Python 的函数

    2.1 函数对象

    • Python 认为一切皆为对象:
    def hi(name="yasoob"):
        return "hi " + name
     
    print(hi())
    # output: 'hi yasoob'
    • 可以将一个函数赋值给一个变量:
    greet = hi
    # 我们这里没有在使用小括号,因为我们并不是在调用hi函数
    # 而是在将它放在greet变量里头。我们尝试运行下这个
     
    print(greet())
    # output: 'hi yasoob'
    • 可以删除一个函数:
    del hi
    print(hi())
    #outputs: NameError
     
    print(greet())
    #outputs: 'hi yasoob'

    2.2 返回值类型为函数

    • 函数的返回值可以为函数对象:
    def hi(name="yasoob"):
        def greet():
            return "now you are in the greet() function"
     
        def welcome():
            return "now you are in the welcome() function"
     
        if name == "yasoob":
            return greet
        else:
            return welcome
     
    a = hi()
    print(a)
    #outputs: <function greet at 0x7f2143c01500>
     
    print(a())
    #outputs: now you are in the greet() function
    • a 为 hi 函数执行的返回值,此时 a 为一个函数对象,执行 a 得到返回值执行的结果

    2.3 参数类型为函数

    • 既然函数对象可以作为返回值,那么不难理解也可以作为参数:
    def hi():
        return "hi yasoob!"
     
    def doSomethingBeforeHi(func):
        print("I am doing some boring work before executing hi()")
        print(func())
     
    doSomethingBeforeHi(hi)
    #outputs:I am doing some boring work before executing hi()
    #        hi yasoob!

    3. 装饰器

    • 上文提到,装饰器本质也是一个返回值为函数类型的函数对象。通过装饰器可以抽离大量与函数本身无关的雷同代码进行复用
    • 一个简单例子,需要在代码中添加日志代码:
    def foo1():
        print('I am foo1')
        logging.info('[foo1] Running')
    • 如果其他函数同样需要执行代码,为避免大量雷同代码,需要重新定义一个函数专门处理日志 ,日志处理完之后再执行真正的业务代码:
    def use_loggine(func)
        logging.info('[%s] Running' % func.__name__)
        func()
    
    def foo2():
        print('I am foo2')
    
    use_logging(foo2)
    • 使用上述方法实现了复用,但是也破坏了带结构,每次直接调用相应函数可以实现的功能,现在需要调用 use_logging 并传入相应函数做为参数,可读性也比较差。因此需要引入装饰器

    3.1 简单装饰器

    def use_logging(func):
        def wrapper(*args, **kwargs):
            logging.warn('[%s] Running' % func.__name__)
            return func(*args, **kwargs)
        return wrapper
    
    def foo3():
        print('I am foo3')
    
    foo3 = use_logging(foo3)
    foo3()
    • 函数 use_logging 就是装饰器,执行真正业务的方法 func 包裹在返回函数里,并重新对参数 foo3 赋值。相当于丰富了 foo3 的功能
    • 在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)
    • @ 符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。其他函数也可以使用此装饰器包装,增加了代码可读性:
    def use_logging(func):
        
        def wrapper(*args, **kwargs):
            logging.warn('[%s] Running' % func.__name__)
            return func(*args, **kwargs)
        
        return wrapper
    
    @use_logging
    def foo4():
        print('I am foo4')
    
    foo4()

    3.2 带参数的装饰器

    • 还可以使用带参数的装饰器
    • 在上面的装饰器调用中,比如 @use_logging,该装饰器唯一的参数就是执行业务的函数
    • 装饰器的语法允许我们在调用时,提供其它参数,比如 @decorator(a):
    def use_logging(level):
    
        def decorator(func):
    
            def wrapper(*args,**kwargs):
                if level == "warn"
                    logging.warn("%s is running"% func.__name__)
                return func(*args)
            return wrapper
    
        return decorator
    
    @use_logging(level="warn")
    def foo5(name='foo'):
        print('I am foo5')
    
    foo5()
    • 上述的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的封装,并返回一个装饰器
    • 我们可以将它理解为一个含有参数的闭包。当我们调用 @use_logging(level=“warn”) 的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中

    3.3 类装饰器

    • 相比函数装饰器,类装饰器具有灵活度大、高内聚和封装性等优点
    • 使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法:
    class FooClass(object):
        def __init__(self, func):
            self._func = func
    
        def __call__(self):
            print ('class decorator runing')
            self._func()
            print ('class decorator ending')
    
    @FooClass
    def foo6():
        print ('I am foo6')
    
    foo6()

    3.4 带 functools.wraps 的装饰器

    • 使用装饰器复用了代码,但是有一个缺点就是原函数元信息不见了,如 docstring、__name、参数列表:
    def logged(func):
        
        def with_logging(*args, **kwargs):
            print(func.__name__ + " was called")
            return func(*args, **kwargs)
        
        return with_logging
    
    @logged
    def foo7(x):
        """do some math"""
        return x + x * x
    
    foo7(8)
    # prints 'foo7 was called
    72'
    • 上述代码等价于如下:
    foo7 = logged(foo7)
    
    print foo7.__name__        # prints 'with_logging'
    print foo7.__doc__        # prints None
    • 可以看出,foo7 的信息会被 with_logging 替代,元信息会被 logged 的元信息替代
    • 使用 functools.wraps 可以把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数元信息与原函数一致:
    from functools import wraps
    def logged(func):
        
        @wraps(func)
        def with_logging(*args, **kargs):
            print(func.__name__ + " was called")
            return func(*args, **kargs)
        return with_logging
    
    @logged
    def foo8(x):
        """do some math"""
        return x + x * x
    
    print foo8.__name__        # prints 'f'
    print foo8.__doc__        # prints 'do some math'

    3.5 内置装饰器

    • 主要包括:@property、@staticmathod、@classmethod

    3.5.1 @property

    • 对于 Class 的某个方法,可以通过 @property 装饰器将其伪装成一个属性,以 实例.方法 方式调用:
    from math import pi
    class Circle:
        def __init__(self,r):
            self.r = r
        @property
        def perimeter(self):
            return 2*pi*self.r
        @property
        def area(self):
            return self.r**2*pi
    
    c1 = Circle(5)
    print(c1.area)     
    # prints '78.5398163397'
    print(c1.perimeter)
    # prints '31.4159265359'
    • 类中的方法名本来是不能用相同名字的,但只要使用 @property 加在 相应方法之前,并且在再次定义同名方法此前加上 @someone.setter、@someone.deleter:
      • 这样在类实例化化后,调用 实例.方法 就相当于调用 @property 包装的方法
      • 调用 实例.方法=val 就相当于调用 @someone.setter 下的方法,并且传进去一个参数 val
      • 调用 del 实例.方法 相当于调用 @someone.deleter 下的方法
    class Person:
         def __init__(self,name):
             self.__name = name
    
         @property
         def name(self):
             return self.__name
    
         @name.deleter
         def name(self):
             del self.__name
    
         @name.setter
         def name(self,new_name):
             self.__name = new_name
    • property 函数可以起到和 @propery 装饰器相同的效果:
    class C(object):
        def __init__(self):
            self._x = None
     
        def getx(self):
            return self._x
     
        def setx(self, value):
            self._x = value
     
        def delx(self):
            del self._x
     
        x = property(getx, setx, delx, "I'm the 'x' property.") # 分别表示获取、设置和删除属性的函数以及 doc 信息
    
    c = C()
    c.x = 1
    print(c.x)
    # prints '1'

    3.5.2 @classmethd

    • 把一个方法 变成一个类中的方法,这个方法就直接可以被类调用,不需要依托任何对象:
    class Someone:
        @classmethod
        def set(cls, new_val):
            cls.__val = new_val
        @classmethod
            def get(cls):
            return cls.__val
        __val = "abc"
    
    print(Someone.get())
    # prints 'abc'
    Someone.set('abcd')
    print(Someone.get())
    # prints 'abcd'
    s1 = Someone()
    s2 = Someone()
    print(s1.get())
    # prints 'abcd'
    print(s2.get())
    # prints 'abcd'
    • 当这个方法的操作只涉及静态属性的时候就应该使用 @classmethod 来装饰这个方法
    • 类似于实例方法要传个 self 代表实例对象,此类方法在传参时,要传一个形参代表当前类,默认 cls

    3.5.3 @staticmethod

    • 如果一个函数既和对象没有关系,也和类没有关系 那么就用 @staticmethod 将这个函数变成一个静态方法:

    class Someone:
        @staticmethod
        def set(new_val):
            Someone.__val = new_val
        @classmethod
        def get(cls):
            print("global __val: %s" % Someone.__val)
            return cls.__val
        __val = "abc"
    • 静态方法就是一个写在类里的普通函数
    • 对象可以调用类方法和静态方法,一般情况下推荐用类名调用
    • 类方法 有一个默认参数 cls 代表这个类 cls
    • 静态方法没有默认的参数,就象函数一样
    • 通过 类.静态方法 调用时不会实例化

    3.5.4 @classmethod 和 @staticmethod 区别

    • @staticmethod  不需要表示自身对象的 self 和自身类的 cls 参数,就和使用普通的函数一样
    • @classmethod  不需要 self 参数,但是第一个参数需要表示自身类的 cls 参数
    • 如果在 @staticmethod 中要调用到这个类的一些属性方法,只能 类名.属性名 类名.方法名
    • 而 @classmethod 因为持有 cls 参数,可以来调用类的属性、类的方法、实例化对象等

    3.6.装饰器执行顺序

    @a
    @b
    @c
    def foo9():
        pass
    
    # 等效于
    
    foo9 = a(b(c(foo9)))

    4. 参考文献

  • 相关阅读:
    C#实现通过拼多多分享微信公众号实现查询优惠券、佣金比率
    淘宝客常用接口整理
    京东联盟开发(1) 商品SKUID采集
    Grafana 安装及 Windows 应用程序服务配置工具 NSSM使用
    Windows Server 2008R2 配置网络负载平衡(NLB)
    IIS 日志分析工具:Log Parser Studio
    curl: (25) Failed FTP upload: 550 解决方案
    搭建TFS 2015 Build Agent环境(四)
    Dump中查看dictionary信息的方法
    Dump中查看DataTime时间方法
  • 原文地址:https://www.cnblogs.com/wangao1236/p/10899711.html
Copyright © 2020-2023  润新知