This article mainly introduces some suggestions for using the Python decorators. The decorators are an important part of Python learning. For more information, see
Basic decorator concepts
As we all know, the decorator is a well-known design model and is often used in AOP (cross-section programming) scenarios. it is more classic in terms of log insertion, performance testing, and transaction processing, web permission verification and Cache.
The Python language provides the decorator syntax (@). The typical decorator implementation is as follows:
@function_wrapper def function(): pass
@ Actually, it is the syntactic sugar proposed by python2.4. for python2.4 earlier versions, there is another equivalent implementation:
def function(): pass function = function_wrapper(function)
Two implementations of the decorator
Function wrapper-classic implementation
def function_wrapper(wrapped): def _wrapper(*args, **kwargs): return wrapped(*args, **kwargs) return _wrapper @function_wrapper def function(): pass
Package-easy to understand
class function_wrapper(object): def __init__(self, wrapped): self.wrapped = wrapped def __call__(self, *args, **kwargs): return self.wrapped(*args, **kwargs) @function_wrapper def function(): pass
Function introspection
When talking about a function, we usually want its attributes to be clearly defined as described in the document, such as _ name _ and _ doc __.
The attributes of a function are changed when the decorator is applied to a function, but this is not what we expect.
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.
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__) function
However, when we want to obtain the parameters (argument) or source code of the encapsulated function, we cannot get the expected results.
import inspect def function_wrapper(wrapped): ... @function_wrapper def function(arg1, arg2): pass >>> print(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)
Packaging method (@ classmethod)
When the package (@ function_wrapper) is applied to @ classmethod, the following exception is thrown:
class Class(object): @function_wrapper @classmethod def cmethod(cls): pass Traceback (most recent call last): File "
", line 1, in
File "
", line 3, in Class File "
", line 2, in wrapper File ".../functools.py", line 33, in update_wrapper setattr(wrapper, attr, getattr(wrapped, attr)) AttributeError: 'classmethod' object has no attribute '__module__'
Because the implementation of @ classmethod lacks certain attributes required by functools. update_wrapper. This is a bug in functools. update_wrapper in python2. version 3.2 has been fixed. for details, refer to http://bugs.python.org/issue3445.
However, another problem occurs during execution in python3:
class Class(object): @function_wrapper @classmethod def cmethod(cls): pass >>> Class.cmethod() Traceback (most recent call last): File "classmethod.py", line 15, in
Class.cmethod() File "classmethod.py", line 6, in _wrapper return wrapped(*args, **kwargs) TypeError: 'classmethod' object is not callable
This is because the wrapper determines that the encapsulated function (@ classmethod) can be called directly, but this is not necessarily the case. The encapsulated function may actually be a descriptor, which means that the function (descriptor) must be correctly bound to an instance for its call. For the descriptor definition, refer to https://docs.python.org/2/howto/descriptor.html.
Summary-simplicity does not mean correctness
Although the method used to implement the decorator is usually very simple, it does not mean that they must be correct and always work properly.
As we can see above, functools. wraps () can help us solve the _ name _ and _ doc _ problems, but it is helpless to obtain function parameters (argument) or source code.