This article mainly introduces some suggestions for the Python decorator, which is an important knowledge in Python learning, and needs a friend to consult with
The basic concept of adorners
We all know that the adorner is a well-known design pattern, often used in AOP (aspect-oriented programming) scene, more classic has inserted log, performance testing, transaction processing, Web permissions check, cache and so on.
The Python language itself provides an adorner syntax (@), and a typical adorner implementation is as follows:
?
1 2 3 |
@function_wrapper def function (): Pass |
@ is actually a syntactic sugar python2.4, which has another equivalent implementation for python2.4 previous versions:
?
1 2 3 4 |
def function (): Pass function = Function_wrapper (function) |
Two kinds of realization of adorner
Function Wrapper-Classic implementation
?
1 2 3 4 5 6 7 8 |
def function_wrapper (wrapped): Def _wrapper (*args, **kwargs): Return wrapped (*args, **kwargs) return _wrapper @function_ Wrapper def function (): Pass |
Class wrapper-Easy to understand
?
1 2 3 4 5 6 7 8 9 |
Class Function_wrapper (object): Def __init__ (self, wrapped): self.wrapped = wrapped def __call__ (self, *args, **kwargs): R Eturn self.wrapped (*args, **kwargs) @function_wrapper def function (): Pass |
Functions (function) introspection
When we talk about a function, we usually want the properties of this function to be clearly defined, as described in its documentation, such as __name__ and __doc__.
When you apply an adorner to a function, the properties of the function change, but that's not what we expect.
?
1 2 3 4 5 6 7 8 9 10 11 |
def function_wrapper (wrapped): Def _wrapper (*args, **kwargs): Return wrapped (*args, **kwargs) return _wrapper @function_ Wrapper def function (): Pass >>> print (function.__name__) _wrapper |
The Python standard library provides functools.wraps () to solve this problem.
?
1 2 3 4 5 6 7 8 9 10 11 12 13-14 |
Import Functools def function_wrapper (wrapped): @functools. Wraps (wrapped) def _wrapper (*args, **kwargs): Return wrapped (*args, **kwargs) return _wrapper @function_wrapper def function (): Pass >>> print (function.__name__) functio N |
However, when we want to get the parameters (argument) of the wrapped function or source code, we don't get the results we want.
?
1 2 3 4 5 6 7 8 9 10 11 12 13-14 |
Import Inspect Def function_wrapper (wrapped): ... @function_wrapper def function (Arg1, arg2): Pass >>> Prin T (Inspect.getargspec (function)) Argspec (args=[], varargs= ' args ', keywords= ' Kwargs ', Defaults=none) >>> Print (Inspect.getsource (function)) @functools. Wraps (wrapped) def _wrapper (*args, **kwargs): Return Wrapped (*args, * * Kwargs) |
Packing class method (@classmethod)
When the wrapper (@function_wrapper) is applied to @classmethod, the following exception is thrown:
?
1 2 3 4 5 6 7 8 9 10 11 12-13 |
Class Class (Object): @function_wrapper @classmethod def cmethod (CLS): Pass Traceback (most recent called last): File < Stdin> ", line 1, in <module> file ' <stdin> ', line 3, in Class File ' <stdin> ', line 2, in wrapper file ".../functools.py", line, in Update_wrapper setattr (wrapper, attr, GetAttr (wrapped, attr)) Attributeerror: ' Classmeth Od ' object has no attribute ' __module__ ' |
Because @classmethod is implemented, some of the attributes required by Functools.update_wrapper are missing. This is the bug,3.2 version of Functools.update_wrapper in Python2 has been repaired, reference http://bugs.python.org/issue3445.
However, executing under Python3, another problem arises:
?
1 2 3 4 5 6 7 8 9 10 11 12-13 |
Class Class (Object): @function_wrapper @classmethod def cmethod (CLS): Pass >>> Class.cmethod () traceback (most Recent call last): File "classmethod.py", line, in <module> class.cmethod () file "classmethod.py", line 6, in _WR Apper return Wrapped (*args, **kwargs) TypeError: ' Classmethod ' object are not callable |
This is because the wrapper determines that the wrapped function (@classmethod) can be invoked directly, but the fact is not necessarily the case. The wrapped function may actually be a descriptor (descriptor), meaning that the function (descriptor) must be properly bound to an instance to make it callable. For the definition of descriptors, you can refer to https://docs.python.org/2/howto/descriptor.html.
Summary-Simple doesn't mean right
Although the methods used to implement adorners are usually simple, this does not mean that they must be correct and always work properly.
As we have seen above, functools.wraps () can help us solve problems with __name__ and __doc__, but there is no way to get a function's arguments (argument) or source code.