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