Expert Python Programming(Third Edition)
上QQ阅读APP看书,第一时间看更新

Introspection preserving decorators

A common mistake when using decorators is not preserving function metadata (mostly docstring and original name) when using decorators. All the previous examples have this issue. They create a new function by composition and return a new object without any respect to the identity of the original decorated object. This makes the debugging of functions decorated that way harder and will also break most of the auto-documentation tools that you may want to use, because the original docstrings and function signatures are no longer accessible.

Let's see this in detail. Let's assume that we have some dummy decorator that does nothing and some other functions decorated with it:

def dummy_decorator(function): 
    def wrapped(*args, **kwargs): 
        """Internal wrapped function documentation.""" 
        return function(*args, **kwargs) 
    return wrapped 
 
 
@dummy_decorator 
def function_with_important_docstring(): 
    """This is important docstring we do not want to lose.""" 

If we inspect function_with_important_docstring() in the Python interactive session, we can see that it has lost its original name and docstring:

>>> function_with_important_docstring.__name__ 
'wrapped' 
>>> function_with_important_docstring.__doc__ 
'Internal wrapped function documentation.' 

A proper solution to this problem is to use the wraps() decorator, provided by the functools module:

from functools import wraps 
 
 
def preserving_decorator(function): 
    @wraps(function) 
    def wrapped(*args, **kwargs): 
        """Internal wrapped function documentation.""" 
        return function(*args, **kwargs) 
    return wrapped 
 
 
@preserving_decorator 
def function_with_important_docstring(): 
    """This is important docstring we do not want to lose."""

With the decorator defined in such a way, all the important function metadata is preserved:

>>> function_with_important_docstring.__name__ 
'function_with_important_docstring.' 
>>> function_with_important_docstring.__doc__ 
'This is important docstring we do not want to lose.' 

Let's see the usage of decorators in the next section.