Professional Python. Luke Sneeringer
Чтение книги онлайн.
Читать онлайн книгу Professional Python - Luke Sneeringer страница 3
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.
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.
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.
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.
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