outline
I python theoretical knowledge is far from teaching level, the main purpose of writing the article is a self-summary, and can not take care of all, please forgive me, the end of the article posted relevant links can be used as a supplement!
The text is divided into three parts decorator theory knowledge, decorator applications, decorator extension
- Decorator basics: non-parsimonious decorators, parametric decorators, functiontools, decorator chains
- Decorator advanced: property, staticmethod, classmethod source code analysis (python code implementation)
Decorator Basics
parameterless decorator
''' Suppose there is a requirement to print the order in which program functions are run The result printed in this case is: foo1 function is starting foo2 function is starting ''' from functools import wraps def NoParamDec(func): The function properties of a #function change when it is loaded by the decorator, and the role of wraps is to ensure that the properties of the decorated function remain unchanged. @wraps(func) def warpper(*args, **kwargs): print('{} function is starting'.format(func.__name__)) return func(*args, **kwargs) return warpper #python black magic omits NoParamDec=NoParamDec(foo1) @NoParamDec def foo1(): foo2() @NoParamDec def foo2(): pass if __name__ == "__main__": foo1()
parametric decorator
''' It is assumed that there is a demand for:Checking the type of function parameters,Allow only matching correct functions to pass through the program This case prints as: ('a', 'b', 'c') -----------------------dividing line------------------------ ERROS!!!!b must be <class 'str'> ERROS!!!!c must be <class 'str'> ('a', 2, ['b', 'd']) ''' from functools import wraps from inspect import signature def typeAssert(*args, **kwargs): deco_args = args deco_kwargs = kwargs def factor(func): The #python standard module class that can be used to check the type of function arguments and only allow certain types to pass the sig = signature(func) # Bind function formal parameters to prescribed types check_bind_args = sig.bind_partial(*deco_args, **deco_kwargs).arguments @wraps(func) def wrapper(*args, **kwargs): # Bind actual parameter values to formal parameters wrapper_bind_args = (*args, **kwargs).() for name, obj in wrapper_bind_args: # Iterate through instances that determine if the actual parameter value is a prescribed parameter if not isinstance(obj, check_bind_args[name]): try: raise TypeError('ERROS!!!!{arg} must be {obj} '.format(**{'arg': name, 'obj': check_bind_args[name]})) except Exception as e: print(e) return func(*args, **kwargs) return wrapper return factor @typeAssert(str, str, str) def inspect_type(a, b, c): return (a, b, c) if __name__ == "__main__": print(inspect_type('a', 'b', 'c')) print('{:-^50}'.format('Dividing Line')) print(inspect_type('a', 2, ['b', 'd']))
Decorator Chain
''' Assume there is a requirement to be: Enter similar code: @makebold @makeitalic def say(): return "Hello" Output: <b><i>Hello</i></b> ''' from functools import wraps def html_deco(tag): def decorator(fn): @wraps(fn) def wrapped(*args, **kwargs): return '<{tag}>{fn_result}<{tag}>'.format(**{'tag': tag, 'fn_result': fn(*args, **kwargs)}) return wrapped return decorator @html_deco('b') @html_deco('i') def greet(whom=''): # Equivalent to geet=html_deco('b')(html_deco('i)(geet)) return 'Hello' + (' ' + whom) if whom else '' if __name__ == "__main__": print(greet('world')) # -> <b><i>Hello world</i></b>
Decorator Advanced
property Principle
Typically, descriptors are object properties with "bound behavior" whose property access has been overridden by methods in the descriptor protocol. These methods are __get__(), __set__(), and __delete__(). If an object defines any of these methods, it is called a descriptor. If an object defines __get__() and __set__(), it is considered a data descriptor. Descriptors that define only __get__() are called non-data descriptors (they are usually used for methods, but other uses are possible).
The attribute lookup priority is:
- class property
- data descriptor
- Instance Properties
- non-data descriptor
- Defaults to __getattr__()
class Property(object): ''' The internal property is implemented in c. Here is a python simulation to implement the property functionality The code is referenced from the official doc documentation ''' def __init__(self, fget=None, fset=None, fdel=None, doc=None): = fget = fset = fdel self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if is None: raise (AttributeError, "unreadable attribute") print('self={},obj={},objtype={}'.format(self,obj,objtype)) return (obj) def __set__(self, obj, value): if is None: raise (AttributeError, "can't set attribute") (obj, value) def __delete__(self, obj): if is None: raise (AttributeError, "can't delete attribute") (obj) def getter(self, fget): return type(self)(fget, , , self.__doc__) def setter(self, fset): return type(self)(, fset, , self.__doc__) def deleter(self, fdel): return type(self)(, , fdel, self.__doc__) class Student( object ): @Property def score( self ): return self._score @ def score( self, val ): if not isinstance( val, int ): raise ValueError( 'score must be an integer!' ) if val > 100 or val < 0: raise ValueError( 'score must between 0 ~ 100!' ) self._score = val if __name__ == "__main__": s = Student() = 60
staticmethod Principle
@staticmethod means: when this method is called, we don't pass an instance of the class to it (as we normally do with methods). This means you can put a function inside a class but you can't access the instance of that class (this is useful when your method does not use the instance).
class StaticMethod(object): "python code implementation of the staticmethod principle" def __init__(self, f): = f def __get__(self, obj, objtype=None): return class E(object): #StaticMethod=StaticMethod(f) @StaticMethod def f( x): return x if __name__ == "__main__": print(('staticMethod Test'))
classmethod
@staticmethod means: when this method is called, we don't pass an instance of the class to it (as we normally do with methods). This means you can put a function inside a class but you can't access the instance of that class (this is useful when your method does not use the instance).
class ClassMethod(object): "python code implementation of the classmethod principle" def __init__(self, f): = f def __get__(self, obj, klass=None): if klass is None: klass = type(obj) def newfunc(*args): return (klass, *args) return newfunc class E(object): #ClassMethod=ClassMethod(f) @ClassMethod def f(cls,x): return x if __name__ == "__main__": print(E().f('classMethod Test'))