Название: Professional Python
Автор: Luke Sneeringer
Издательство: John Wiley & Sons Limited
Жанр: Зарубежная образовательная литература
isbn: 9781119070788
isbn:
Running the code from either registry's run_all
method gives the following results:
Notice that the run_all
method is able to take arguments, which it then passes to the underlying functions when they are run.
These decorators are very simple because the decorated function is passed through unmodified. However, sometimes you want additional functionality to run when the decorated method is executed. You do this by returning a different callable that adds the appropriate functionality and (usually) calls the decorated method in the course of its execution.
A Simple Type Check
Here is a simple decorator that ensures that every argument the function receives is an integer, and complains otherwise:
What is happening here?
The decorator itself is requires_ints
. It accepts one argument, decorated
, which is the decorated callable. The only thing that this decorator does is return a new callable, the local function inner
. This function replaces the decorated method.
You can see this in action by declaring a function and decorating it with requires_ints
:
Notice what you get if you run help(foo)
:
The inner
function has been assigned to the name foo
instead of the original, defined function. If you run foo(3, 5)
, the inner
function runs with those arguments. The inner function performs the type check, and then it runs the decorated method simply because the inner function calls it using return decorated(*args, **kwargs), returning 8. Absent this call, the decorated method would have been ignored.
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.
СКАЧАТЬ