Professional Python. Luke Sneeringer

Читать онлайн книгу.

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.

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 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.

Execution-Time Wrapping Code

      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


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