Skip to content
BetaDE
Lesson 32 of 35Python

Python · Lesson 32 · 30 minutes

Decorators

What you'll learn

  • Pass functions around as values
  • Write your own decorator
  • Extend a function with @

Functions are values too

In Python you can store functions in variables and pass them to other functions, just like numbers or text:

def loud(text):
    return text.upper() + "!"

def twice(function, value):
    return function(function(value))

say = loud
print(say("hello"))
print(twice(loud, "hey"))

A function that wraps functions

A decorator gets a function and gives back a new, extended function. This one measures how long a function takes:

import time

def stopwatch(function):
    def inner(*args):
        start = time.perf_counter()
        result = function(*args)
        duration = time.perf_counter() - start
        print(f"{function.__name__} took {duration:.4f} s")
        return result
    return inner

def sum_up_to(n):
    return sum(range(n))

sum_up_to = stopwatch(sum_up_to)
print(sum_up_to(1_000_000))

*args means: “all the values that are passed, however many there are”.

The @ notation

sum_up_to = stopwatch(sum_up_to) is a bit clumsy. That’s why there’s a shortcut with @ right above the function – it does exactly the same:

def shout(function):
    def inner(*args):
        return function(*args).upper()
    return inner

@shout
def greet(name):
    return f"hello {name}"

print(greet("codelix"))

Remember: You’ll see decorators everywhere in big projects – for example @app.route("/") in web frameworks or @pytest.fixture in testing.

Quick check

Before you start the task: what does this program print? Type your guess – then Python works it out.

def loud(function):
    def wrapper():
        return function().upper() + "!"
    return wrapper


@loud
def greet():
    return "hello"


print(greet())

Your task

Your turn

Write a decorator count_calls that counts how often a function is called. The number should be stored in the attribute calls of the new function. Decorate greet with it and call it three times.

def count_calls(function):
    def inner(*args):
        # count and call the real function
        pass
    inner.calls = 0
    return inner


@count_calls
def greet(name):
    return f"Hello {name}"


print(greet("Mia"))
print(greet("Ben"))
print(greet("Lea"))
print(greet.calls)

Solve it without hints to earn a ⭐ on your progress page.

Show solution

Try it yourself first – mistakes teach you the most. Here is one possible solution:

def count_calls(function):
    def inner(*args):
        inner.calls += 1
        return function(*args)
    inner.calls = 0
    return inner


@count_calls
def greet(name):
    return f"Hello {name}"


print(greet("Mia"))
print(greet("Ben"))
print(greet("Lea"))
print(greet.calls)