Python Decorators
A decorator wraps a function to add extra behaviour, without changing the function's own code.
Functions Are Values
In Python a function is an object, just like a number or a string. You can store it in a variable and pass it to another function.
Example
def shout(text):
return text.upper() + "!"
speak = shout
print(speak("hello"))
def run_twice(func, value):
return func(func(value))
print(run_twice(shout, "hi"))HELLO! HI!!
Notice speak = shout has no brackets. We copy the function itself, we do not call it.
A Function Inside a Function
A function can create another function and return it.
Example
def make_discount(percent):
def apply(price):
return price - price * percent / 100
return apply
eid_sale = make_discount(20)
print(eid_sale(5000))
print(eid_sale(1200))4000.0 960.0
The inner function apply remembers percent even after make_discount has finished. This is called a closure, and it is the key idea behind decorators.
Your First Decorator
A decorator is a function that takes a function and returns a new, wrapped function.
Example
def announce(func):
def wrapper():
print("--- start ---")
func()
print("--- end ---")
return wrapper
@announce
def greet():
print("Assalam o Alaikum!")
greet()--- start --- Assalam o Alaikum! --- end ---
The line @announce above def greet() is short for writing greet = announce(greet) after the function. From now on, calling greet() really calls wrapper().
Decorating Any Function
Most functions take arguments. Use *args and **kwargs in the wrapper so it accepts anything, and return the result of the original function.
Example
import functools
def log_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with {args} {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log_call
def bill(price, qty, tax=0):
return price * qty + tax
bill(250, 4, tax=180)
print(bill.__name__)Calling bill with (250, 4) {'tax': 180}
bill returned 1180
billAlways add @functools.wraps(func) to your wrapper. Without it, the decorated function loses its real name and docstring, which makes errors and help text confusing.
A Useful Example: Timing
Decorators are perfect for jobs you want on many functions: logging, timing, checking permissions, retrying.
Example
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
seconds = time.perf_counter() - start
print(f"{func.__name__} took about {seconds:.1f} s")
return result
return wrapper
@timer
def slow_report():
time.sleep(0.5)
return "Report ready"
print(slow_report())slow_report took about 0.5 s Report ready
Decorators With Arguments
To pass settings to a decorator, add one more outer function.
Example
import functools
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def cheer(name):
print(f"Well done, {name}!")
cheer("Ayesha")Well done, Ayesha! Well done, Ayesha! Well done, Ayesha!
Built-in Decorators You Will Meet
| Decorator | What it does |
|---|---|
@property | Lets a method be read like an attribute |
@staticmethod | A method that needs no self |
@classmethod | A method that gets the class (cls) instead of the object |
@functools.cache | Remembers results so repeated calls are instant |
Example
import functools
class Product:
def __init__(self, price):
self.price = price
@property
def price_with_tax(self):
return round(self.price * 1.18)
p = Product(1000)
print(p.price_with_tax)
@functools.cache
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(80))1180 23416728348467685
p.price_with_tax has no brackets because @property turns the method into a read-only attribute. Without @cache, fib(80) would take years. With it, the answer comes at once.
Web frameworks use decorators a lot. In Flask, for example, @app.route("/") connects a function to a web address.
Exercise
Exercise
What is @announce above def greet() short for?
