Register to get access to free programming courses with interactive exercises

Decorators Python: Functions

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.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

Bookmate
Health Samurai
Dualboot
ABBYY
Suggested learning programs
profession
new
Developing web applications with Django
10 months
from scratch
under development
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.