Python装饰器各种类型详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yhy1271927580/article/details/72758577

装饰器

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


这里写图片描述

装饰器总结

发现很多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 sleeping
from time import time as nowTime
from 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

# 装饰的对象为函数
@timeit
def 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 wraps
import 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 wraps
def 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

# 装饰的对象是类
@singleton
class A:
    def __init__(self):
        self.name = 'yhy'
# 实例化装饰后的类
a = A()
print(id(a))  # 4321677152
b = 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))  # 4321677152
b = 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 wraps

class 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, partial


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

# 装饰对象为类
@Singleton
class A:
    def __init__(self):
        self.name = 'yhy'


print(A().name)
# a和b是同一个实例
a = A()
b = A()

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

展开阅读全文

没有更多推荐了,返回首页