Название | Professional Python |
---|---|
Автор произведения | Luke Sneeringer |
Жанр | Зарубежная образовательная литература |
Серия | |
Издательство | Зарубежная образовательная литература |
Год выпуска | 0 |
isbn | 9781119070788 |
Preserving the help
It often is not particularly desirable to have a decorator steamroll your function's docstring or hijack the output of help
. Because decorators are tools for adding generic and reusable functionality, they are necessarily going to be more vague. And, generally, if someone using a function is trying to run help
on it, he or she wants information about the guts of the function, not the shell.
The solution to this problem is actually … a decorator. Python implements a decorator called @functools.wraps
that copies the important introspection elements of one function onto another function.
Here is the same @requires_ints
decorator, but it adds in the use of @functools.wraps
:
The decorator itself is almost entirely unchanged, except for the addition of the second line, which applies the @functools.wraps
decorator to the inner
function. You must also import functools
now (which is in the standard library). You will also notice some additional syntax. This decorator actually takes an argument (more on that later).
Now you apply the decorator to the same function, as shown here:
Here is what happens when you run help(foo)
now:
You see that the docstring for foo
, as well as its method signature, is what is read out when you look at help
. Underneath the hood, however, the @requires_ints
decorator is still applied, and the inner
function is still what runs.
Depending on which version of Python you are running, you will get a slightly different result from running help
on foo
, specifically regarding the function signature. The previous paste represents the output from Python 3.4. However, in Python 2, the function signature provided will still be that of inner
(so, *args
and **kwargs
rather than x
and y
).
User Verification
A common use case for this pattern (that is, performing some kind of sanity check before running the decorated method) is user verification. Consider a method that is expected to take a user as its first argument.
The user should be an instance of this User
and AnonymousUser
class, as shown here:
A decorator is a powerful tool here for isolating the boilerplate code of user verification. A @requires_user
decorator can easily verify that you got a User
object and that it is not an anonymous user.
This decorator applies a common, boilerplate need – the verification that a user is logged in to the application. When you implement this as a decorator, it is reusable and more easily maintainable, and its application to functions is clear and explicit.
Note that this decorator will only correctly wrap a function or static method, and will fail if wrapping a bound method to a class. This is because the decorator ignores the expectation to send self
as the first argument to a bound method.
Output Formatting
In addition to sanitizing input to a function, another use for decorators can be sanitizing output from a function.
When you're working in Python, it is normally desirable to use native Python objects when possible. Often, however, you want a serialized output format (for example, JSON). It is cumbersome to manually convert to JSON at the end of every relevant function, and (and it's not a good idea, either). Ideally, you should be using the Python structures right up until serialization is necessary, and there may be other boilerplate that happens just before serialization (such as or the like).
Decorators provide an excellent, portable solution to this problem. Consider the following decorator that takes Python output and serializes the result to JSON:
Apply the @json_output
decorator to a trivial function, as shown here:
Run the function in the Python shell, and you get the following:
Notice that you got back a string that contains valid JSON. You did not get back a dictionary.
The beauty of this decorator is in its simplicity. Apply it to a function, and suddenly a function that did return a Python dictionary, list, or other object now returns its JSON-serialized version.
You might ask, “Why is this valuable?” After all, you are adding a one-line decorator that essentially removes a single line of code – a call to json.dumps
. However, consider the value of having this decorator as the application's needs expand.
For example, what if certain exceptions should be trapped and output specifically formatted JSON, rather than having the exception bubble up and traceback? Because you have a decorator, that functionality is very easy to add.
By augmenting the @json_output
decorator with this error handling, you have added it to any function where the decorator was already applied. This is part of what makes decorators so valuable. They are very useful tools for code portability and reusability.
Now, if a function decorated with @json_output
raises a JSONOutputError
, you will get this special error handling. Here is a function that does:
Running the error
function in the Python interpreter gives you the following:
Note that only the JSONOutputError
exception class (and any subclasses) receives this special handling. Any other exception is passed through normally, and generates a traceback. Consider this function:
When you run it, you will get the traceback you expect, as shown here:
This reusability and maintainability is part of what makes decorators valuable. Because a decorator is being used for a reusable, generally applicable concept throughout the application (in this case, JSON serialization), the decorator becomes the place for housing that functionality as needs arise that are applicable whenever that concept is used.
Essentially, decorators are tools to avoid repeating yourself, and part of their value is in providing hooks for future maintenance.
This can be accomplished without the use of decorators. Consider the example of requiring a logged-in user. It is not difficult to write a function that does this and simply place it near the top of functions that require that functionality. The decorator is primarily syntactic sugar. The syntactic sugar has value, though. Code is read more often than it is written, after all, and it is easy to locate decorators at a glance.
Logging
One final example of execution-time wrapping of code is a general-use logging function. Consider the following decorator that causes the function call, timings, and result to be logged:
When applied to a function, this decorator runs that function normally, but uses the Python logging
module to log out information about the function call after