python实现装饰器、描述符
概要
本人python理论知识远达不到传授级别,写文章主要目的是自我总结,并不能照顾所有人,请见谅,文章结尾贴有相关链接可以作为补充
全文分为三个部分装饰器理论知识、装饰器应用、装饰器延申
- 装饰理基础:无参装饰器、有参装饰器、functiontools、装饰器链
- 装饰器进阶:property、staticmethod、classmethod源码分析(python代码实现)
装饰器基础
无参装饰器
''' 假定有一个需求是:打印程序函数运行顺序 此案例打印的结果为: foo1functionisstarting foo2functionisstarting ''' fromfunctoolsimportwraps defNoParamDec(func): #函数在被装饰器装时后,其函数属性也会改变,wraps作用就是保证被装饰函数属性不变 @wraps(func) defwarpper(*args,**kwargs): print('{}functionisstarting'.format(func.__name__)) returnfunc(*args,**kwargs) returnwarpper #python黑魔法省略了NoParamDec=NoParamDec(foo1) @NoParamDec deffoo1(): foo2() @NoParamDec deffoo2(): pass if__name__=="__main__": foo1()
有参装饰器
''' 假定有一个需求是:检查函数参数的类型,只允许匹配正确的函数通过程序 此案例打印结果为: ('a','b','c') -----------------------分割线------------------------ ERROS!!!!bmustbeERROS!!!!cmustbe ('a',2,['b','d']) ''' fromfunctoolsimportwraps frominspectimportsignature deftypeAssert(*args,**kwargs): deco_args=args deco_kwargs=kwargs deffactor(func): #python标准模块类,可以用来检查函数参数类型,只允许特定类型通过 sig=signature(func) #将函数形式参数和规定类型进行绑定 check_bind_args=sig.bind_partial(*deco_args,**deco_kwargs).arguments @wraps(func) defwrapper(*args,**kwargs): #将实际参数值和形式参数进行绑定 wrapper_bind_args=sig.bind(*args,**kwargs).arguments.items() forname,objinwrapper_bind_args: #遍历判断是否实际参数值是规定参数的实例 ifnotisinstance(obj,check_bind_args[name]): try: raiseTypeError('ERROS!!!!{arg}mustbe{obj}'.format(**{'arg':name,'obj':check_bind_args[name]})) exceptExceptionase: print(e) returnfunc(*args,**kwargs) returnwrapper returnfactor @typeAssert(str,str,str) definspect_type(a,b,c): return(a,b,c) if__name__=="__main__": print(inspect_type('a','b','c')) print('{:-^50}'.format('分割线')) print(inspect_type('a',2,['b','d']))
装饰器链
''' 假定有一个需求是: 输入类似代码: @makebold @makeitalic defsay(): return"Hello" 输出: Hello ''' fromfunctoolsimportwraps defhtml_deco(tag): defdecorator(fn): @wraps(fn) defwrapped(*args,**kwargs): return'<{tag}>{fn_result}<{tag}>'.format(**{'tag':tag,'fn_result':fn(*args,**kwargs)}) returnwrapped returndecorator @html_deco('b') @html_deco('i') defgreet(whom=''): #等价于geet=html_deco('b')(html_deco('i)(geet)) return'Hello'+(''+whom)ifwhomelse'' if__name__=="__main__": print(greet('world'))#->Helloworld
装饰器进阶
property原理
通常,描述符是具有“绑定行为”的对象属性,其属性访问已经被描述符协议中的方法覆盖。这些方法是__get__()、__set__()和__delete__()。如果一个对象定义这些方法中的任何一个,它被称为一个描述符。如果对象定义__get__()和__set__(),则它被认为是数据描述符。仅定义__get__()的描述器称为非数据描述符(它们通常用于方法,但是其他用途也是可能的)。
属性查找优先级为:
- 类属性
- 数据描述符
- 实例属性
- 非数据描述符
- 默认为__getattr__()
classProperty(object): ''' 内部property是用c实现的,这里用python模拟实现property功能 代码参考官方doc文档 ''' def__init__(self,fget=None,fset=None,fdel=None,doc=None): self.fget=fget self.fset=fset self.fdel=fdel self.__doc__=doc def__get__(self,obj,objtype=None): ifobjisNone: returnself ifself.fgetisNone: raise(AttributeError,"unreadableattribute") print('self={},obj={},objtype={}'.format(self,obj,objtype)) returnself.fget(obj) def__set__(self,obj,value): ifself.fsetisNone: raise(AttributeError,"can'tsetattribute") self.fset(obj,value) def__delete__(self,obj): ifself.fdelisNone: raise(AttributeError,"can'tdeleteattribute") self.fdel(obj) defgetter(self,fget): returntype(self)(fget,self.fset,self.fdel,self.__doc__) defsetter(self,fset): returntype(self)(self.fget,fset,self.fdel,self.__doc__) defdeleter(self,fdel): returntype(self)(self.fget,self.fset,fdel,self.__doc__) classStudent(object): @Property defscore(self): returnself._score @score.setter defscore(self,val): ifnotisinstance(val,int): raiseValueError('scoremustbeaninteger!') ifval>100orval<0: raiseValueError('scoremustbetween0~100!') self._score=val if__name__=="__main__": s=Student() s.score=60 s.score
staticmethod原理
@staticmethodmeans:whenthismethodiscalled,wedon'tpassaninstanceoftheclasstoit(aswenormallydowithmethods).Thismeansyoucanputafunctioninsideaclassbutyoucan'taccesstheinstanceofthatclass(thisisusefulwhenyourmethoddoesnotusetheinstance).
classStaticMethod(object): "python代码实现staticmethod原理" def__init__(self,f): self.f=f def__get__(self,obj,objtype=None): returnself.f classE(object): #StaticMethod=StaticMethod(f) @StaticMethod deff(x): returnx if__name__=="__main__": print(E.f('staticMethodTest'))
classmethod
@staticmethodmeans:whenthismethodiscalled,wedon'tpassaninstanceoftheclasstoit(aswenormallydowithmethods).Thismeansyoucanputafunctioninsideaclassbutyoucan'taccesstheinstanceofthatclass(thisisusefulwhenyourmethoddoesnotusetheinstance).
classClassMethod(object): "python代码实现classmethod原理" def__init__(self,f): self.f=f def__get__(self,obj,klass=None): ifklassisNone: klass=type(obj) defnewfunc(*args): returnself.f(klass,*args) returnnewfunc classE(object): #ClassMethod=ClassMethod(f) @ClassMethod deff(cls,x): returnx if__name__=="__main__": print(E().f('classMethodTest'))