Professional Python. Luke Sneeringer
Читать онлайн книгу.align="center">
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 function and register it with more than one registry, as shown here:
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