Decorators are a "syntactic sugar" for a function that takes another function as an argument.
Usually the thing returned is another function, but it technically doesn't have to be.
Functions that take functions as arguments and return functions are referred to as higher-order functions.
@some_decorator
def foo():
...
# is equivalent to...
def foo():
...
foo = some_decorator(foo)
Decorators are often used in frameworks like Flask and FastAPI to create productive high-level abstractions.
Learning to write them can seem overwhelming at first, but once you grok the concept, writing decorators will become natural.
Just be careful not to abuse the pattern. As useful as decorators are, they add a layer of indirection that can make our code more difficult to understand.
Every function in python will have a __name__
attribute that is its string name.
Let's write a decorator that prints a function's name at the time of decoration.
def print_name(func):
print(f"The function being decorated is named {func.__name__}.")
@print_name
def greet():
return f"Hello, world."
greet()
The function being decorated is named greet.
Traceback (most recent call last):
at block 5, line 5
TypeError: 'NoneType' object is not callable
Why did our greet
function raise an Exception?
We decorated greet
with our print_name
decorator, but print_name
doesn't return anything.
So we basically did the equivalent of writing greet = None
.
Let's fix that.
def print_name(func):
print(f"The function being decorated is named {func.__name__}.")
return func # < we return the original function
@print_name
def greet():
return f"Hello, world."
for _ in range(3):
print(greet())
# notice how we'll only print the description once, at the time we decorate `greet`. not on every invocation
The function being decorated is named greet.
Hello, world.
Hello, world.
Hello, world.
What if we wanted to do something with the decorated function's input or output?