Python装饰器各种类型详解

来源:互联网 发布:手机测量软件安卓版 编辑:程序博客网 时间:2024/06/07 10:15

装饰器

装饰器有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的,而且还能将函数和类的功能进行扩充,实现被装饰对象的功能扩展


这里写图片描述

装饰器总结

发现很多Python新手对装饰器以及具有描述器功能的装饰器理解不是很全面,下面通过自我的总结,对Python中的装饰器进行了分类,并且给出分类后的实例代码,希望对大家理解装饰器有帮助。装饰器总而言之:装饰器就是对函数(方法)或者的功能进行扩充,将扩充之后的函数(方法)或者再返回。因此,在调用这个函数(方法)或者的时候,就不会调用元素的函数(方法)或者了,而是调用返回的新函数(新方法)或者新类


一: 装饰器自身为函数


(一):被装饰的对象为函数,且不带参数
对以下代码简要说明: @timeit装饰器对sleep函数进行了装饰,这是一个装饰器不带参数的装饰器,当sleep函数调用的时候,调用的并不是我们看到的原始的sleep函数,而是装饰过后的sleep函数。这个装饰的过程会发生在调用sleep函数之前发生。装饰的过程:原生的sleep函数作为参数传递到timeit装饰器函数的fn参数,通过@wraps这个装饰器将fn的属性赋值给底下需要返回的wrap函数,最后返回wrap函数,由此可间,wrap就是装饰过后的sleep函数了。那么在调用新sleep函数的时候,就是调用wrap函数,sleep函数的参数1,被wrap函数的*args、**kwargs这两个可变参数接收。整个装饰的目的就是将原生的sleep函数,扩充了一个print(nowTime() - start)的过程。
from time import sleep as sleepingfrom time import time as nowTimefrom functools import wraps# 装饰器为函数def timeit(fn):    @wraps(fn)    def wrap(*args,**kwargs):        start = nowTime()        ret = fn(*args,**kwargs)        print(nowTime() - start)        return ret    return wrap# 装饰的对象为函数@timeitdef sleep(n):    sleeping(n)    print("我睡了{}秒".format(n))    return n# 调用装饰后的sleep函数    sleep(1)print(sleep.__name__)

(二):被装饰的对象为函数,且带参数
对以下代码简要说明: @timeit装饰器对sleep函数进行了装饰,这是一个装饰器是带参数的装饰器,这个装饰器由于需要传递参数,因此需要两层装饰,第一层是接受传递的参宿,第二层是接收传递进来的需要装饰的函数当sleep函数调用的时候,调用的并不是我们看到的原始的sleep函数,而是装饰过后的sleep函数。这个装饰的过程会发生在调用sleep函数之前发生。装饰的过程:装饰器第一次接收cpu_time参数,然后返回一个dec装饰器,而这个dec装饰器会被再次调用,传入参数是原生的sleep函数,原生的sleep函数作为参数传递到dec装饰器函数的fn参数,通过@wraps这个装饰器将fn的属性赋值给底下需要返回的wrap函数,最后返回wrap函数,由此可间,wrap就是装饰过后的sleep函数了。那么在调用新sleep函数的时候,就是调用wrap函数,sleep函数的参数1,被wrap函数的*args、**kwargs这两个可变参数接收。整个装饰的目的就是将原生的sleep函数,扩充了一个time_func = time.clock if cpu_time else time.timeprint(nowTime() - start)的过程。
from functools import wrapsimport time# 装饰器自身为函数,且装饰器接受参数def timeit(cpu_time=False):    time_func = time.clock if cpu_time else time.time    def dec(fn):        @wraps(fn)        def wrap(*args,**kwargs):            start = time_func()            ret = fn(*args,**kwargs)            print(time_func() - start)            return ret        return wrap    return dec# 装饰对象为函数@timeit(False)def sleep(n):    time.sleep(n)    return n# 调用装饰后的sleep函数 sleep(1)print(sleep.__name__)

(三):被装饰的对象为类,且不带参数
对以下代码简要说明:这是一个基于装饰器的单例设计模式。通过装饰器装饰这个类,使得类在初始化的时候始终将初始化实例赋值给instance,而instance是装饰器的一个实例对象,通过实例赋值,使得instance始终占有同一个内存空间,也就实现了单例设计
from functools import wrapsdef singleton(cls):    # 在装饰器中声明一个变量,用于保存类的实例,那么这个实例对象将始终是通一个实例对象    instance = None    @wraps(cls)    def wrap(*args,**kwargs):        # 使用nonlocal关键字将作用域扩展到上一级,拿到上一级的instance变量        nonlocal instance        # 如果instance非空,那么将instance通过cls类,也就是A进行初始化        if instance is None:              instance = cls(*args,**kwargs)        return instance    return wrap# 装饰的对象是类@singletonclass A:    def __init__(self):        self.name = 'yhy'# 实例化装饰后的类a = A()print(id(a))  # 4321677152b = A()print(id(b))  # 4321677152

(四):被装饰的对象为类,且带参数
对以下代码简要说明:下面代码为带参数的。可以对比看出,装饰器自身为函数,且带参数与不带参数的区别就是:带参数的装饰器比不带参数的装饰器多了一层。为什么?因为第一层装饰用来解释装饰器的参数,第二层才能够接收被装饰的对象
from functools import wraps# 装饰器自身为函数def singleton(instance=False):    name = 'yhy did it' if instance else 'yhy didn\'t it'    def dec(cls):        instance = None        @wraps(cls)        def wrap(*args,**kwargs):            nonlocal instance            print(name)            if instance is None:                instance = cls(*args,**kwargs)            return instance        return wrap    return dec# 装饰的对象是类@singleton(True)class A:    def __init__(self):        self.name = 'yhy'# 实例化装饰后的类a = A()print(id(a))  # 4321677152b = A()print(id(b))  # 4321677152
对以下代码简要说明:通过setattr魔术方法,对Person类进行了修改,这里的name作为类属性,name = TypeCheck(name, required_type),这样就将Person类进行了改造,使得Person类有了两个类变量,一个是name = TypeCheck(‘name’, required_type), 另一个是age = TypeCheck(‘age’, required_type),因此,Person(‘yhy’,18) 初始化的时候,self.name 中的name不是实例变量而是类变量,会调用描述器TypeCheck,赋值的时候,就会调用__set__方法,取值的时候会调用__get__方法。这个例子是一个经典的装饰器装饰类,使得被装饰的新类中的类变量绑定了描述器,因此,在给类变量赋值的时候,赋值过程是经过描述器赋值的,取值的时候也是通过描述器赋值的
from functools import wrapsclass TypeCheck:    def __init__(self, srcType, dstType):        self.srcType = srcType        self.dstType = dstType    # instance == a , cls == A    def __get__(self, instance, cls):        if instance is None:            return self        return instance.__dict__[self.srcType]    def __set__(self, instance, value):        if isinstance(value,self.dstType):            instance.__dict__[self.srcType] = value        else:            raise TypeError('{} should be {}'.format(value,self.dstType))# 装饰器自身是一个函数def typeassert(**kwargs):    def dec(cls):        def wraps(*args):            for name, required_type in kwargs.items():                setattr(cls, name, TypeCheck(name, required_type))            return cls(*args)  # 这里是实例化新的Person类后返回实例对象,也就是p        return wraps    return dec# 装饰对象是一个类,且带参数@typeassert(name=str, age=int)class Person:    def __init__(self, name, age):        self.name = name        self.age = age# 实例化新的Person类,这里相对于调用的是wraps函数p = Person('yhy',18)print(p.name)print(p.age)# 装饰器修改后的Person类为下面这个新的Person类,因此实例化Person的时候,调用的是下面这个新的Person类class Person:    name = TypeCheck('name',str)    age = TypeCheck('age',int)    def __init__(self, name: str, age: int):        self.name = name        self.age = age

二:装饰器自身为类


(一):被装饰的对象为函数
1:对以下代码简要说明:这里是通过装饰器装饰了一个类中的方法,也就是函数。从代码里可以看到,装饰的add方法被传递到装饰器的method参数里,通过wraps函数将method的属性赋值给self。当调用a.add()方法的时候,由于装饰器的功能就是返回一个扩充被装饰对象的函数,那么装饰器内部实现了__get__方法,此时装饰器扮演一个描述器的角色,当调用a.add()方法的时候,装饰器内部调用__get__方法,返回一个装饰后的add方法。这个例子是一个@staticmethod的实现,也是一个经典的装饰器通过描述器的特性装饰函数的例子。虽然装饰器没有扩充函数什么功能,但是Python解释器不会给add()方法传递self参数了,这也算一个扩充吧,如果没有@staticmethod装饰add()方法,add方法定义的时候需要一个add(self)参数,来接受解释器传递的self实例,那add(self)就是一个实例方法,不是静态方法,因为Add调用这个add()方法的时候解释器不会传递self实例。那么使用装饰器装饰后的add方法,解释器将不会传递self,因此Add.add()调用是可以的
from functools import wraps,partial# 装饰器自身为类class staticmethod:    def __init__(self, method):        wraps(method)(self)    def __get__(self, instance, cls):        # 通过partial函数返回一个带有None参数的add函数,因此在调用a.add()就不需要传递参数了        return partial(self.__wrapped__)class Add:    def __init__(self, name, value):        self.name = name        self.value = value    # 这里装饰的是一个函数    @staticmethod    def add():        print('i am static method')a = Add('yhy', 25)a.add()Add.add()
2:对以下代码简要说明:这是一个@Classmethod装饰器的实现,与@staticmethod的实现类似。partial函数将method方法进行了扩充,返回的method方法在原生的add基础之上返回一个带cls的method方法,因此在调用method的时候,由于x参数有默认值’y’,可以给a.method()传递一个参数或传递参数
from functools import wraps, partialclass Classmethod:    def __init__(self,method):        wraps(method)(self)    def __get__(self, instance, cls):        # 通过partial函数返回一个带cls的method方法,        return partial(self.__wrapped__, cls)class C:    @Classmethod    def method(cls,x='y'):        print(cls)        print(x)# 调用装饰器返回的新method方法C.method('yhy')c = C()c.method()c.method('yhy')输出为:<class '__main__.C'>yhy<class '__main__.C'>y<class '__main__.C'>yhy

(二):被装饰的对象为函数,且带参数
对以下代码简要说明:@Require({‘root’, ‘admin’})装饰器装饰一个类,并且装饰器带参数。从结构上来说,只要类装饰器带有__call__函数,那么装饰器返回的就是这个__call__函数,但是这个装饰器带有参数,因此,返回的是wrap函数。也就是说,装饰器装饰后的新函数为wrap函数,当reboot()调用的时候就是调用wrap函数,如果权限认证通过就返回fn()函数(这里的fn()是调用原生的reboot()函数)
from functools import wraps# 假定权限def get_permissions():    return {'root'}# 装饰器自身是类class Require:    def __init__(self,permissions):        self.permissions = permissions    # 被装饰的类传递到fn    def __call__(self, fn):        @wraps(fn)        def wrap(*args, **kwargs):            if len(set(self.permissions).intersection(get_permissions())) <= 0:                raise Exception('Permissions denied')            return fn(*args, **kwargs)        return wrap# 装饰器装饰的对象是函数@Require({'root','admin'})def reboot():    pass

(三):被装饰的对象为类,且不带参数
对以下代码简要说明:这里的A通过Singleton类装饰器传递给参数cls,但是这个类装饰器没有__get__方法,无法返回装饰后的类,但是有一个__call__方法。因此A()就相当于调用了装饰器的__call__方法。在__call__方法里面self.__wrapped__就是cls,也就是A类,实例化A类,创建一个实例self.instance,最后返回。因此,调用A().name,就是self.instance.name
from functools import wrapsclass Singleton:    # 这里的初始化函数只会调用一次,当第二次装饰的时候,这一步就滤过了    # 因此,第二次装饰的时候,不会再执行__init__方法,直接返回一个Singleton实例对象,    # 使得第二次调用__call__方法,发现self.instance有值,那么就直接返回了    def __init__(self,cls):        wraps(cls)(self)        self.instance = None    def __call__(self, *args, **kwargs):        if self.instance is None:            self.instance = self.__wrapped__(*args,**kwargs)        return self.instance# 装饰对象为类@Singletonclass A:    def __init__(self):        self.name = 'yhy'print(A().name)# a和b是同一个实例a = A()b = A()

小总结:以上总结过程是本人在写Python代码的心得,将装饰器的应用形式进行了一个简单的分类。装饰器在Python中应用非常广泛,需要灵活应用。希望以上总结对小伙伴们有帮助,也希望大神批评指正

原创粉丝点击