Professional Python. Luke Sneeringer

Читать онлайн.
Название Professional Python
Автор произведения Luke Sneeringer
Жанр Зарубежная образовательная литература
Серия
Издательство Зарубежная образовательная литература
Год выпуска 0
isbn 9781119070788



Скачать книгу

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.

      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