Professional Python. Luke Sneeringer
Чтение книги онлайн.

Читать онлайн книгу Professional Python - Luke Sneeringer страница 6

СКАЧАТЬ if you run the do_nothing function now, you get a JSON block back with indentation and newlines added, as shown here:

      How Does This Work?

      But wait. If json_output is not a decorator, but a function that returns a decorator, why does it look like it is being applied as a decorator? What is the Python interpreter doing here that makes this work?

      More explanation is in order. The key here is in the order of operations. Specifically, the function call (json_output(indent=4)) precedes the decorator application syntax (@). Thus, the result of the function call is used to apply the decorator.

      The first thing that is happening is that the interpreter is seeing the function call for json_output and resolving that call (note that the boldface does not include the @):

      All the json_output function does is define another function, actual_decorator, and return it. As the result of that function, it is then provided to @, as shown here:

      Now, actual_decorator is being run. It declares another local function, inner, and returns it. As previously discussed, that function is then assigned to the name do_nothing, the name of the decorated method. When do_nothing is called, the inner function is called, runs the decorated method, and JSON dumps the result with the appropriate indentation.

      The Call Signature Matters

      It is critical to realize that when you introduced your new, altered json_output function, you actually introduced a backward-incompatible change.

      Why? Because now there is this extra function call that is expected. If you want the old json_output behavior, and do not need values for any of the arguments available, you still must call the method.

      In other words, you must do the following:

      Note the parentheses. They matter, because they indicate that the function is being called (even with no arguments), and then the result is applied to the @.

      The previous code is not —repeat, not —equivalent to the following:

      This presents two problems. It is inherently confusing, because if you are accustomed to seeing decorators applied without a signature, a requirement to supply an empty signature is counterintuitive. Secondly, if the old decorator already exists in your application, you must go back and edit all of its existing calls. You should avoid backward-incompatible changes if possible.

      In a perfect world, this decorator would work for three different types of applications:

      ● @json_output

      ● @json_output()

      ● @json_output(indent=4)

      As it turns out, this is possible, by having a decorator that modifies its behavior based on the arguments that it receives. Remember, a decorator is just a function and has all the flexibility of any other function to do what it needs to do to respond to the inputs it gets.

      Consider this more flexible iteration of json_output:

      This function is endeavoring to be intelligent about whether or not it is currently being used as a decorator.

      First, it makes sure it is not being called in an unexpected way. You never expect to receive both a method to be decorated and the keyword arguments, because a decorator is always called with the decorated method as the only argument.

      Second, it defines the actual_decorator function, which (as its name suggests) is the actual decorator to be either returned or applied. It defines the inner function that is the ultimate function to be returned from the decorator.

      Finally, it returns the appropriate result based on how it was called:

      ● If decorated_ is set, it was called as a plain decorator, without a method signature, and its responsibility is to apply the ultimate decorator and return the inner function. Here again, observe how decorators that take arguments are actually working. First, actual_decorator(decorated_) is called and resolved, then its result (which must be a callable, because this is a decorator) is called with inner provided as its only argument.

      ● If decorated_ is not set, then this was called with keyword arguments instead, and the function must return an actual decorator, which receives the decorated method and returns inner. Therefore, the function returns actual_decorator outright. This is then applied by the Python interpreter as the actual decorator (which ultimately returns inner).

      Why is this technique valuable? It enables you to maintain your decorator's functionality as previously used. This means that you do not have to update each case where the decorator has been applied. But you still get the additional flexibility of being able to add arguments in the cases where you need them.

      Decorating Classes

      Remember that a decorator is, fundamentally, a callable that accepts a callable and returns a callable. This means that decorators can be used to decorate classes as well as functions (classes are callable, after all).

      Decorating classes can have a variety of uses. They can be particularly valuable because, like function decorators, class decorators can interact with the attributes of the decorated class. A class decorator can add or augment attributes, or it can alter the API of a class to provide a distinction between how a class is declared versus how its instances are used.

      You might ask, "Isn't the appropriate way to add or augment attributes of a class through subclassing?" Usually, the answer is "yes." However, in some situations an alternative approach may be appropriate. Consider, for example, a generally applicable feature that may apply to many classes in your application that live in distinct places in your class hierarchies.

      By way of example, consider a feature of a class such that each instance knows when it was instantiated, and instances are sorted by their creation times. This has general applicability across many different classes, and requires the addition of three attributes – the instantiation timestamp, and the __gt__ and __lt__ methods.

      You have multiple ways to go about adding this. Here is how you can do it with a class decorator:

      The first thing that is happening in this decorator is that you are saving a copy of the class's original __init__ method. You do not need to worry about whether the class has one. Because object has an __init__ method, that attribute's presence is guaranteed. Next, you create a new method that will be assigned to __init__, and this method first calls the original and then does one piece of extra work, saving the instantiation timestamp to self._created.

      It СКАЧАТЬ