Type hints and attributes for decorators

We construct decorated functions in two stages. The first stage is the def statement with an original definition.

A def statement provides a name, parameters, defaults, a docstring, a code object, and a number of other details. A function is a collection of 11 attributes, defined in section 3.2 of the Python Standard Library, which is the standard type hierarchy.

The second stage involves the application of a decorator to the original definition. When we apply a decorator, (@d), to a function (F), the effect is as if we have created a new function, . The name, F, is the same, but the functionality can be different, depending on the kinds of features that have been added, removed, or modified. Generally, we can write the following code:

@decorate 
def function(): 
    pass 

The decorator is written immediately in front of the function definition. What happens to implement this can be seen by the following:

def function(): 
    pass 
function = decorate(function) 

The decorator modifies the function definition to create a new function. The essential technique here is that a decorator function accepts a function and returns a modified version of that function. Because of this, a decorator has a rather complex type hint.

This second style, function=decorate(function), also works with functions created by assigning a lambda to a variable. It works with callable objects, also. The @decorate notation only works with the def statement.

When there are multiple decorators, they are applied as nested function calls. Consider the following example:

@decorator1
@decorator2
def function(): ...

This is equivalent to function=decorator1(decorator2(function)). When decorators have side effects, the order of applying the decorations will matter. In the Flask framework, for example, the @app.route decoration should always be at the top of the stack of decorators so that it is applied last and includes the results of other decorators' behaviors.

The following is a typical set of type hints required to define a decorator:

from typing import Any, Callable, TypeVar, cast

FuncType = Callable[..., Any]
F = TypeVar('F', bound=FuncType)

def my_decorator(func: F) -> F:
...

We've defined a function type, FuncType, based on the Callable type hint. From this, the type variable, F, is derived as a generic description of anything that adheres to the FuncType protocol. This will include functions, lambdas, and callable objects. The decorator function, my_decorator(), accepts a parameter, func, with the type hint of F, and returns a function, using the type hint of F. What's essential is that any kind of object with the callable protocol can be described as having an upper boundary of the very generic FuncType. We've omitted the details of my_decorator() for now. This snippet is intended to show the general approach to type hints.

The decorator for a class is simpler, because the signature is def class_decorator(class: Type) -> Type: .... There are a few ways to create classes, and the upper limit is already defined as the type hint Type.

Now, let's examine the different attributes of a function.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset