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.fixturein 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)- Something to think about
inner has to return the result of the real function – return
function(*args) - Which command helps?
Increase the counter inside inner with
inner.calls+=1 - Gap code – fill in the ___
def count_calls(function): def inner(*args): inner.calls += ___ return ___ 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🔒 Unlocks after all hints or 2 checks
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)