Professional Python. Luke Sneeringer
Чтение книги онлайн.
Читать онлайн книгу Professional Python - Luke Sneeringer страница 5
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 it completes. Now, suddenly, you have (extremely rudimentary) logging of any function where this decorator is applied.
Unlike the previous examples, this decorator does not alter the function call in an obvious way. No cases exist where you apply this decorator and get a different result from the decorated function than you did from the undecorated function. The previous examples raised exceptions or modified the result if this or that check did not pass. This decorator is more invisible. It does some under-the-hood work, but in no situation should it change the actual result.
Variable Arguments
It is worth noting that the @json_output
and @logged
decorators both provide inner
functions that simply take, and pass on with minimal investigation, variable arguments and keyword arguments.
This is an important pattern. One way that it is particularly important is that many decorators may be used to decorate plain functions as well as methods of classes. Remember that in Python, methods declared in classes receive an additional positional argument, conventionally known as self
. This does not change when decorators are in use. (This is why the requires_user
decorator shown earlier does not work on bound methods within classes.)
For example, if @json_result
is used to decorate a method of a class, the inner
function is called and it receives the instance of the class as the first argument. In fact, this is fine. In this case, that argument is simply args[0]
, and it is passed to the decorated method unmolested.
One thing that has been consistent about all the decorators enumerated thus far is that the decorators themselves appear not to take any arguments. As discussed, there is an implied argument – the method that is being decorated.
However, sometimes it is useful to have the decorator itself take some information that it needs to decorate the method appropriately. The difference between an argument passed to the decorator and an argument passed to the function at call time is precisely that. An argument to the decorator is processed once, when the function is declared and decorated. By contrast, arguments to the function are processed when that function is called.
You have already seen an example of an argument sent to a decorator with the repeated use of @functools.wraps
. It takes an argument – the method being wrapped, whose help and docstring and the like should be preserved.
However, decorators have implied call signatures. They take one positional argument – the method being decorated. So, how does this work?
The answer is that it is complicated. Recall the basic decorators that have execution-time wrapping of code. They declare an inner method in local scope that they then return. This is the callable returned by the decorator. It is what is assigned to the function name. Decorators that take arguments add one more wrapping layer to this dance. This is because the decorator that takes the argument is not actually the decorator. Rather, it is a function that returns the decorator, which is a function that takes one argument (the decorated method), which then decorates the function and returns a callable.
That sounds confusing. Consider the following example where a @json_output
decorator is augmented to ask about indentation and key sorting:
So, what has happened here, and why does this work?
This is a function, json_output
, which accepts two arguments (indent
and sort_keys
). It returns another function, called actual_decorator
, which is (as its name suggests) intended to be used as a decorator. That is a classic decorator – a callable that accepts a single callable (decorated
) as an argument and returns a callable (inner
).
Note that the inner
function has changed slightly to accommodate the indent
and sort_keys
arguments. These arguments mirror similar arguments accepted by json.dumps
, so the call to json.dumps
accepts the values provided to indent
and sort_keys
in the decorator's signature and provides them to json.dumps
in the antepenultimate line.
The inner
function is what ultimately makes use of the indent
and sort_keys
arguments. This is fine, because Python's block scoping rules allow for this. It also is not a problem that this might be called with different values for inner
and sort_keys
, because inner
is a local function (a different copy is returned each time the decorator is used).
Applying the json_output
function looks like this:
And