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

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

СКАЧАТЬ previous two-decorator example is syntactically equivalent to the following:

      In both cases, the also_decorated_by decorator comes first as a human reads the code. However, the decorators are applied bottom to top for the same reason that the functions are resolved from innermost to outermost. The same principles are at work.

      In the case of a traditional function call, the interpreter must first resolve the inner function call in order to have the appropriate object or value to send to the outer call.

      With a decorator, first the add function is created normally.

      Then, the @decorated_by decorator is called, being sent the add function as its decorated method.

      The @decorated_by function returns its own callable (in this case, a modified version of add). That value is what is then sent to @also_decorated_by in the final step.

      When applying decorators, it is important for you to remember that they are applied bottom to top. Many times, order does matter.

      Where Decorators Are Used

      The standard library includes many modules that incorporate decorators, and many common tools and frameworks make use of them for common functionality.

      For example, if you want to make a method on a class not require an instance of the class, you use the @classmethod or @staticmethod decorator, which is part of the standard library. The mock module (which is used for unit testing, and which was added to the standard library in Python 3.3) allows the use of @mock.patch or @mock.patch.object as a decorator.

      Common tools also use decorators. Django (which is a common web framework for Python) uses @login_required as a decorator to allow developers to specify that a user must be logged in to view a particular page, and uses @permission_required for applying more specific permissions. Flask (another common web framework) uses @app.route to serve as a registry between specific URIs and the functions that run when the browser hits those URIs.

      Celery (a common Python task runner) uses a complex @task decorator to identify a function as an asynchronous task. This decorator actually returns an instance of a Task class, which illustrates how decorators can be used to make a very convenient API.

      Why You Should Write Decorators

      Decorators provide an excellent way to say, “I want this specific, reusable piece of functionality in these specific places.” When written well, they are modular and explicit.

      The modularity of decorators (you can apply or remove them from functions or classes easily) makes them ideal for avoiding the repetition of boilerplate setup and teardown code. Similarly, because decorators interact with the decorated function itself, they excel at registering functions elsewhere.

      Also, decorators are explicit. They are applied, in-place, to all callables where they are needed. This is valuable for readability, and therefore for debugging. It is obvious exactly what is being applied and where.

      When You Should Write Decorators

      Several very good use cases exist for writing decorators in Python applications and modules.

Additional Functionality

      Probably the most common reason to write a decorator is if you want to add additional functionality before or after the decorated method is executed. This could include use cases such as checking authentication or logging the result of a function to a consistent location.

Data Sanitization or Addition

      A decorator could also sanitize the values of arguments being passed to the decorated function, to ensure consistency of argument type, or that a value conforms to a specific pattern. For example, a decorator could ensure that the values sent to a function conform to a specific type, or meet some other validation standard. (You will see an example of this shortly, a decorator called @requires_ints.)

      A decorator can also transform or sanitize data that is returned from a function. A valuable use case for this is if you want to have functions that return native Python objects (such as lists or dictionaries), but ultimately receive a serialized format (such as JSON or YAML) on the other end.

      Some decorators actually provide additional data to a function, usually in the form of additional arguments. The @mock.patch decorator is an example of this, because it (among other things) provides the mock object that it creates as an additional positional argument to the function.

Function Registration

      Many times, it is useful to register a function elsewhere – for example, registering a task in a task runner, or a function with a signal handler. Any system in which some external input or routing mechanism decides what function runs is a candidate for function registration.

      Writing Decorators

      Decorators are simply functions that (usually) accept the decorated callable as their only argument, and that return a callable (such as in the previous trivial example).

      It is important to note that the decorator code itself runs when the decorator is applied to the decorated function, rather than when the decorated function is called. Understanding this is critical, and will become very clear over the course of the next several examples.

An Initial Example: A Function Registry

      Consider the following simple registry of functions:

      The register method is a simple decorator. It appends the positional argument, decorated to the registry variable, and then returns the decorated method unchanged. Any method that receives the register decorator will have itself appended to registry.

      If you have access to the registry, you can easily iterate over it and execute the functions inside.

      The answers list at this point would now contain [3, 5]. This is because the functions are executed in order, and their return values are appended to answers.

      Several less-trivial uses for function registries exist, such as adding “hooks” into code so that custom functionality can be run before or after critical events. Here is a Registry class that can handle just such a case:

      One thing worth noting about this class is that the register method – the decorator – still works the same way as before. It is perfectly fine to have a bound method as a decorator. It receives self as the first argument (just as any other bound method), and expects one additional positional argument, which is the decorated method.

      By making several different registry instances, you can have entirely separate registries. It is even possible to take the same СКАЧАТЬ