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.time
和print(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中应用非常广泛,需要灵活应用。希望以上总结对小伙伴们有帮助,也希望大神批评指正
阅读全文
1 0
- Python装饰器各种类型详解
- Python装饰器详解
- python装饰器详解
- Python装饰器详解
- Python 装饰器详解
- python装饰器详解
- python @装饰器 详解
- Python 装饰器详解
- python装饰器详解
- Python装饰器详解
- Python装饰器详解
- 详解Python装饰器
- python装饰器详解
- python 装饰器详解
- 详解python装饰器
- Python装饰器----类型转换
- Python中的装饰器详解
- 详解Python的装饰器
- 求解汉诺塔问题
- Servlet是什么?有什么用?
- 以书的前言作为博客的开篇
- Kali信息收集
- JVM学习笔记(二)jvm类加载机制
- Python装饰器各种类型详解
- dnspod上提示 域名 NS 地址还未修改
- 引用方式
- insert子查询
- c++第五次实验
- python-pip操作基本指令介绍
- linux下wget下载整个网站
- Storm线程进程分配方法
- 淘淘商城系列——SSM框架整合之逆向工程