Decorators
A decorator is a function that wraps another function to add behavior, without changing the original's body. You apply one with the @ syntax sitting on top of a def.
The shape is always the same: take a function, define an inner wrapper that does something extra and then calls the original, and return the wrapper.
def shout(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
@shout
def greet(name):
return f"hi {name}"
print(greet("ada")) # HI ADAWriting @shout above greet is just shorthand for greet = shout(greet). From then on, the name greet points at the wrapper.
The *args, **kwargs in the wrapper let it accept whatever arguments the original takes, then forward them along. That keeps the decorator generic.
functools.wraps
Wrapping replaces the function, so the original's name and docstring get hidden behind wrapper. Fix that by decorating the wrapper itself with functools.wraps(func), which copies the metadata over. Always do this on real decorators:
import functools
def double(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs) * 2
return wrapper
@double
def add(a, b):
"""Add two numbers."""
return a + b
print(add(3, 4)) # 14
print(add.__name__) # 'add' (preserved by wraps)Write a decorator double that doubles the return value of any function it wraps. Use functools.wraps so the wrapped function keeps its __name__. Then apply it with @double to a function add(a, b) that returns a + b, so add(3, 4) gives 14 and add.__name__ is still "add".
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.