Imagine you wanted to be able to print all the results from calling a function.
However, you do not want to modify it but get a universal tool suitable for use with any functions instead. Let us implement such a function, which we will use a higher-order function for:
def printing(function):
def inner(*args, **kwargs):
result = function(*args, **kwargs)
print('result =', result)
return result
return inner
def add_one(x):
return x + 1
add_one = printing(add_one)
y = add_one(10)
# => result = 11
y
# 11
First, let us deal with the printing
function. This function creates an inner closure that:
- Accepts arguments
- Applies a function to them
- Prints the result
- Immediately returns it
Note that in the definition of inner,
we used the arguments *args, **kwargs
, which the function passes to the closed function function
without modifying them. It is how Python declares omnivorous functions that take any combination of arguments.
Now let us look at an example of using printing
. We replaced the function with a wrapped version by assigning a new value to the old function name here. Yes, functions in Python are ordinary variables in the scope we declare them in. We chose name printing because these wrappers for functions often bear similar names.
After all, in the sense of the behavior that the wrapper adds to the original function, it serves as an addition to this function. And printing (add_one)
is super easy to read.
This wrapping is a fairly common operation in code that widely uses higher-order functions. The authors of the Python language even introduced a special syntax to make it more convenient to use wrapper functions. We can refer to wrapper functions and wrapping syntax together as decorators.
The syntax of decorators
We can apply the printing
decorator to the add_one
function like so:
@printing
def add_one(x):
return x + 1
We write the decorator name on the line preceding the function header and put an @
before the name. Once we apply the decorators like this, we no longer need to re-assign the function add_one = printing(add_one)
.
Using even one decorator becomes more convenient with this syntax, but you can use as many decorators as you like. It will look like this:
@logging
@printing
@cached
def foo():
# …
What would be equivalent to code:
foo = cached(foo)
foo = printing(foo)
foo = logging(foo)
# or one line
foo = logging(printing(cached(foo)))
Pay attention and remember: wrapping occurs first in the wrappers closest to the function name, inside out: cached
, then printing
, then logging
.
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.