Decorators
在编写程序中存在大量代码重复的问题,其在编写过程会十分枯燥无聊,并且后续难以维护.元编程涉及编写操作其他代码的代码问题.其相应的应用示例为宏(Marco),包装器(wrapper),切面(aspect).因此其本质上是对代码进行操作.元编程的核心动力是DRY原则(Don't
Repeat Yourself).
当发现存在大量代码结构类似的情况,则可以用元编程.与C/C++的宏在编译前处理文本,Python的元编程是在运行的时候操作对象(func,class).其会更加的灵活,更加的动态.装饰器是一个创建另一个函数包装器的函数.包装器则是一个全新的函数,但是他的工作原理和原有函数是完全相同的,接受相同的函数参数可以返回相同的返回值,但是可以增加一些额外的操作.装饰器本质上是接受函数作为参数并返回新函数的高阶函数.理想的装饰器应该是对被装饰器的调用者是透明的,也就是即使换成被装饰了的函数,其调用和返回也不应该导致代码出错.而额外的输出则是由包装器函数实现,但仍然依靠原始函数获得结果
1 2 3 4 5 6 7 8 9 10 def add (x,y ): return x+y def logged (func ): def wrapper (*args,**kwargs ): print ('Calling' ,func.__name__) return func(*args,**kwargs) return wrapper log_add=logged(add) print (log_add)print (log_add(3 ,4 ))
这里我们发现这个logged函数的内部函数wrapper内部调用了func,所以其实wrapper函数就是前面介绍过的闭包函数,这样可以使得wrapper能够记住他要包装的那个专有函数.上面的代码中需要为每个函数编写log_func=logged(func),这要求我们额外多写很多的重复代码.
1 2 3 4 @logged def add (x,y ): return x+y
这相当于是将add的指向函数进行了修改,从原始函数修改到包装器函数.当我们使用装饰器替代函数,会添加一些函数功能,这也就是所谓的装饰.装饰器不修改原函数,而是做一个包装.原函数不变,只是调用路径发生了改变.常用的装饰器如下
1 2 3 4 @staticmethod @classmethod @property name=decorator(name)
装饰器可以在任意时间定义一个涉及函数操作的高阶函数,其主要应用是调试诊断,避免代码重复,启用/禁用可选功能.
Advanced Decorators
高阶装饰器指的是多重装饰器,装饰器和元数据和带参数的装饰器.装饰器可以多次嵌套使用,
1 2 3 4 5 6 @foo @bar @spam def func (): pass
执行顺序:装饰器是从下向上的应用;调用顺序:调用函数时,从外向内调用.但是多层嵌套会使得调试更困难,错误堆栈也会更长.但是这个潜逃需要十分注意装饰器的上下顺序,不同的顺序可能会导致结果的不同.当定义一个函数,同时会存储一些额外信息(名称,文档字符串等)
1 2 3 4 5 6 def add (x, y ): 'Adds x and y' return x + y print (add.__name__)print (add.__doc__)help (add)
内省基础:这些属性(name,doc,module)是工具依赖的基础.装饰器则不会保持这些元数据,因为装饰器实际指向了另一个函数,所以出错的时候函数堆栈跟踪的是包装器函数.
1 2 3 4 5 6 @logged def add (x, y ): 'Adds x and y' return x + y print (add.__name__)print (add.__doc__)
我们可以手动加上复制函数的元数据的代码.
1 2 3 4 5 6 7 8 9 10 11 12 13 def logged (func ): def wrapper (*args,**kwargs ): print ('Calling' ,func.__name__) return func(*args,**kwargs) wrapper.__name__=func.__name__ wrapper.__doc__=func.__doc__ return wrapper @logged def add (x, y ): 'Adds x and y' return x + y print (add.__name__)print (add.__doc__)
当然也可以用如下方式自动的复制函数元数据,
1 2 3 4 5 6 7 8 9 10 11 12 13 from functools import wrapsdef logged (func ): @wraps(func ) def wrapper (*args,**kwargs ): print ('Calling' ,func.__name__) return func(*args,**kwargs) return wrapper @logged def add (x, y ): 'Adds x and y' return x + y print (add.__name__)print (add.__doc__)
这里会自动将原函数的元数据复制到wrapper函数上去.从上面的代码来看,wraps装饰器是能够接受参数的,所以我们接下来介绍带参数的装饰器.
1 2 3 4 @decorator(x,y,z ) def func (): pass
装饰器函数必须返回一个函数,这个函数会用于创建包装器函数.对于这类带参数的装饰器,实际上有三层的参数嵌套,外层接受装饰器参数,中层接受被装饰函数,内层接受调用参数.其调用链由decorator(x,y,z)返回中层函数,其会被被装饰函数调用.其外部函数用于接受参数,内部函数则是标准的装饰器代码.因此带参数的装饰器更加通用,并且也可以将带参数的装饰器实例化为无参数的装饰器变量.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def logmsg (message ): def logged (func ): @wraps(func ) def wrapper (*args,**kwargs ): print (message.format (name=func.__name__)) return func(*args,**kwargs) return wrapper return logged @logmsg('You called {name}' ) def add (x,y ): return x+y print (add(3 ,4 ))logged=logmsg('You called {name}' ) @logged def add (x,y ): return x+y print (add(3 ,4 ))
Class Decorator
装饰器不仅可以应用在函数上,还可以应用在类定义.
1 2 3 4 5 @decorator class Myclass : def foo : pass
所以也就是装饰器操作或包装一个类.类装饰器一般常用于自动注册类,验证类结构或者批量修改类方法.大多数类装饰器会检查类定义或做一些特殊处理.类装饰器的语法结构如下所示
1 2 3 4 def decorator (cls ): return cls
这里我们可以发现类装饰器通常不会修改原始类并且返回.函数装饰器通常会返回一个新的函数,而类装饰器通常直接修改传入的类对象cls并返回相应的类对象.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def logged_getattr (cls ): orig_getattribute=cls.__getattribute__ def __getattribute__ (self,name ): print ("Getting:" ,name) return orig_getattribute(self,name) cls.__getattribute__=__getattribute__ return cls @logged_getattr class MyClass : def __init__ (self,name,size ): self.name=name self.size=size def foo (self ): pass s=MyClass(10 ,9 ) print (s.name)print (s.size)s.foo()
这里我们提供了一个类装饰器的例子,他的作用是在调用类成员的时候,会宣告查看的类成员名称.
现代的Python3.6以上的版本提供了__init_subclass__方法,他其实比前面介绍的类装饰器更好用,所以我们在此加以解释一下.通常父类不知道子类继承的情况,但是定义了__init_subclass__的类,是可以在类被继承的时候察觉到子类的存在,并且对子类进行自动化的操作.
1 2 3 4 5 6 7 8 9 10 class PluginBase : subclasses = [] @classmethod def __init_subclass__ (cls, **kwargs ): super ().__init_subclass__(**kwargs) cls.subclasses.append(cls) class VideoPlugin (PluginBase ): pass class AudioPlugin (PluginBase ): pass print (PluginBase.subclasses)
这里的cls表示刚刚创建的子类,这其实也是表示父类可以获取子类的信息.
Type
Python中的任意值都有自己的类型,并且类型其实也是一个可调用对象,我们可以调用他来创建一个同类型的对象.
1 2 3 4 5 6 7 8 9 10 x=43 print (type (x))s='Hello' print (type (s))items=[1 ,2 ,3 ] print (type (items))a=list () print (a)b=tuple (items) print (b)
不仅如此,我们自定义创建的类在Python中也是类型的一种.所以类可以自定义一个新类型,他的调用方式和内置类型一样.
1 2 3 4 5 class Spam : pass s=Spam() print (type (s))print (type (Spam))
类是创建的实例的类型,类也是创建实例的可调用对象.我们对于类调用type函数可以得知,类的类型其实是type类.换言之,类其实是type类的一个实例.而type类本身则是由他们自己表示,所以我们定义自己的类其实在Python内部是由type类做的处理,因此我们称type类为元类,简单来说他就是所有类的类.
我们对类的定义过程进行一个解构,首先我们常见的类定义方式如下所示,
1 2 3 4 5 6 7 class Spam (object ): def __init__ (self,name ): self.name=name def yow (self ): print ("Yow!" ,self.name) s=Spam('Test' ) s.yow()
但实际上我们可以将他分成三步:第一步我们先定义一些需要的函数方法,
1 2 3 4 def __init__ (self,name ): self.name=name def yow (self ): print ("Yow!" ,self.name)
第二步我们则是构造方法字典,他的键名是方法名,而他的值则是上面定义的函数对象,两者可以不同,但是不建议,因为会使得程序维护困难,
1 methods={'__init__' :__init__,'yow' :yow}
最后我们调用type函数来构造一个类
1 2 Foo=type ('Foo' ,(object ,),methods) type (clsname,(Base,),methods)
这里的第一个参数clsname表示想要创建的类名,第二个参数则表示需要的基类元组,第三个参数则是方法字典.
在此基础上我们给出一个利用type构造函数和命名空间字典来创建类的方式.首先,我们需要准备命名空间,语法如下所示,
1 __dict__=type .__prepare__('Spam' ,(object ,))
在正常的class语句的开始,Python会先调用元类的__prepare__方法,其作用是创建一个用于保存类成员(属性和方法)的字典,他通常返回一个空的dict,但在某些复杂的元类编程中,会返回一个OrderedDict或其他特殊映射.这里传入基类的原因,一是用来检查父类中是否有自定义的元类,如果有,那么Python就会调用其元类的方法而不是type的默认方法,二则是默认情况下会返回一个字典,但是某些高级模块会要求返回有序字典,这样的话,可以保证顺序.
第二步用于填充类信息
1 2 __dict__['__qualname__' ] = 'Spam' __dict__['__module__' ] = 'modulename'
这里其实就是在模拟Python编译器自动完成的工作,__qualname__表示类的限定名,__module__表示类的模块名,如果不手动设置,那么手动创建的类可能会丢失这些元数据.
第三步是执行类体代码也是最为关键的一步
1 2 3 4 5 6 7 body=''' def __init__(self,name): self.name=name def yow(self): print("Yow!",self.name) ''' exec (body, globals (), __dict__)
body表示一个包含函数定义的字符串,exec则是在__dict__这个命名空间里面执行字符串代码.执行后,__dict__中会多出两个键:__init__和yow,它们分别对应定义的函数对象.
第四步,形成真正的类
1 Spam = type ('Spam' , (object ,), __dict__)
整体代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 body=''' def __init__(self,name): self.name=name def yow(self): print("Yow!",self.name) ''' __dict__=type .__prepare__('Spam' ,(object ,)) print (__dict__)__dict__['__qualname__' ] = 'Spam' __dict__['__module__' ] = 'modulename' exec (body,globals (),__dict__)print (__dict__)Spam = type ('Spam' , (object ,), __dict__)
创建类的类称之为元类,type则是元类的一个实例.Python提供了一个Hook,允许你覆盖类创建步骤.我们在创建类的时候可以选择使用不同于type的元类.使用这个,你可以完全自定义类在创建时发生的事情.其相关的语法调用如下所示,
1 2 3 4 5 6 7 class Spam (metaclass=type ): def __init__ (self,name ): self.name=name def yow (self ): print ("Yow!" ,self.name) s=Spam('Test' ) s.yow()
我们发现在类创建的时候提供了一个元类参数,其提供一个metaclass关键字参数,在此我们可以设置用于创建类对象的类,也就是所谓的元类选择.只是默认来说我们直接使用type元类,因此我们平时见到的元类并不多,当然我们也可以设置成自己定义的元类.上面提到的用法其实是Python3的语法,在Python2中,我们需要用如下的语法结构,
1 2 3 4 5 6 class Spam : __metaclass__=type def __init__ (self,name ): self.name=name def yow (self ): print ('Yow!' ,self.name)
如果我们不显式的设置元类,那么Python会直接使用父类的元类创建.
1 2 3 4 5 class Spam (object ): def __init__ (self,name ): self.name=name def yow (self ): print ('Yow!' ,self.name)
我们提供一种自定义新元类的方式,我们可以通过继承type类并且重定义类的方法,如__new__,__prepare__等等.
1 2 3 4 5 6 7 class mytype (type ): @staticmethod def __new__ (meta,name,bases,methods ): print ('Creating class:' ,name) print ('Base classes:' ,bases) print ('Methods:' ,list (methods)) return super ().__new__(meta,name,bases,methods)
基于上面定义的元类,我们可以定义一个新的根对象,
1 2 class myobject (metaclass=mytype): pass
为了使用我们上面定义的新元类,我们可以通过继承根类对象来定义自己的类,元类的new方法是用于创建类对象本身,而类的new方法则是创建一个未初始化的类实例对象.
1 2 3 4 5 class Spam (myobject ): def __init__ (self, name ): self.name = name def yow (self ): print ("Yow!" , self.name)
Application
元类允许改变类定义过程本身,设置类定义环境,改变实例创建.元类允许监控和操作类定义,他有四个比较关键的拦截点,分别如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 type .__prepare__(name,bases)type .__new__(type ,name,bases,dict )cls.__new__(cls,*args,**kwargs) type .__init__(cls,name,bases,dict )cls.__init__(*args,**kwargs) type .__call__(cls,*args,**kwargs)
类定义方法的重复检查
1 2 3 4 5 6 7 8 9 10 11 12 class dupedict (dict ): def __setitem__ (self,key,val ): assert key not in self, '%s duplicated' % key super ().__setitem(key,val) class dupemeta (type ): def __prepare__ (name,bases ): return dupedict() class A (metaclass=dupemeta): def bar (self ): pass def bar (self ): pass
对类中的每个方法都做装饰器操作,
1 2 3 4 5 6 7 8 9 10 11 12 def decorator (func ): print ('Decorating' ,func.__name__) def wrapper (*args,**kwargs ): print ('Calling' ,func.__name__) return func(*args,**kwargs) return wrapper class meta (type ): def __new__ (cls,name,bases,methods ): for key,val in methods: if callable (val): methods[key]=decorator(val) return super ().__new__(cls,name,bases,methods)
实例创建
1 2 3 4 5 6 7 class meta (type ): def __call__ (cls,*args,**kwargs ): print ('Calling instance of' ,cls) return super ().__call__(*args,**kwargs) class Spam (metaclass=meta): def __init__ (self, name ): self.name = name
单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class meta (type ): _instance=None def __call__ (cls,*args,**kwargs ): if not cls._instance: cls._instance=super ().__call__(*args,**kwargs) return cls._instance class Spam (metaclass=meta): def __init__ (self,name ): print ('Creating Spam' ) self.name=name s=Spam('Test' ) s1=Spam('Test1' ) print (s1.name)print (id (s)==id (s1))