Decorators and Higher-order functions
When you see @cl.on_message or @app.route("/api"), that @ symbol is a decorator. Decorators wrap a function with extra behavior (logging, timing, registration) and power Chainlit, FastAPI, Flask, and every modern Python framework.
Decorator wrapping
How a decorator wraps a function with extra behavior
# Functions are objects (you can pass them around)
def shout(text):
return text.upper() + "!"
def whisper(text):
return text.lower() + "..."
# Choose a function based on a condition
formatter = shout
print(formatter("hello")) # HELLO!
# Pass a function to another function
def apply(func, text):
return func(text)
print(apply(whisper, "HELLO")) # hello...In Python, functions are objects. You can assign them to variables and pass them as arguments. This is the foundation of decorators.
@decorator above a function is syntactic sugar for: my_func = decorator(my_func). The decorator receives your function, wraps it with extra behavior, and returns the wrapped version. Your original function is replaced by the wrapper.
import time
# Writing a decorator
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"{func.__name__} took {elapsed:.3f}s")
return result
return wrapper
@timer
def slow_search(items, query):
time.sleep(0.1) # Simulate work
return [item for item in items if query.lower() in item.lower()]
menu = ["Mushroom Burger", "Quinoa Bowl", "Cashew Mac", "Tofu Tacos"]
results = slow_search(menu, "o")
print(f"Found: {results}")The timer decorator wraps slow_search: it records the start time, calls the original function, then prints the elapsed time. This is exactly how Chainlit registers message handlers.
Quiz: Quiz
Loading practice…
Fill in the blanks: Complete the decorator
Loading practice…
Validation checklist: Decorators checklist
Loading practice…
Checkpoint: Oop Python checkpoint
Loading practice…
Timed quiz: Oop speed round
Loading practice…