PEP 614 in 90 seconds — any expression after @ now decorates
Subscripts, attribute chains, conditionals — all legal after @.
Pre-3.9, the grammar after `@` allowed only `name` (`@cache`), `name.attr` (`@app.route`), or `name(...)` (`@retry(3)`). Anything fancier — subscript, conditional, lambda — was a `SyntaxError`: ```python DECORATORS = [cache, retry, log_args] @DECORATORS[0] # SyntaxError pre-3.9 def handler(req): ... ``` PEP 614 (Python 3.9, 2020) lifted that to **any expression** that evaluates to something callable. The grammar matches whatever you'd write in a regular call site.
What's now legal: - `@DECORATORS[0]` — subscript - `@decorators_by_env["prod"]` — dict lookup - `@(retry if is_flaky else passthrough)` — conditional - `@a.b.c.d.e` — already worked, now without the implicit-call ambiguity What the rule isn't: you STILL need the result to be callable with `(func)`. Putting `@42` after a function gets you `TypeError: 'int' object is not callable` at definition time. PEP 614 is about parser flexibility, not runtime magic.
def cache(fn):
seen = {}
def inner(*a):
if a not in seen:
seen[a] = fn(*a)
return seen[a]
return inner
# Pre-3.9, this was a SyntaxError:
#
# DECORATORS = [cache, ...]
# @DECORATORS[0]
# def slow_fn(x): ...
#
# Workaround: bind to a name first
my_cache = cache
@my_cache
def slow_fn(x):
return x * 2def add_one(fn):
def inner(x):
return fn(x) + 1
return inner
def add_ten(fn):
def inner(x):
return fn(x) + 10
return inner
DECORATORS = [add_one, add_ten]
# Subscript is legal — picks from a list
@DECORATORS[0]
@DECORATORS[1]
def number(x):
return x
# Conditional / attribute chains / lambda calls also legal:
# @(retry if FLAKY else passthrough)
# @router.get("/users/{uid}") (already worked, now without grammar quirks)🎯 Predict the output
What does this print? Decorators apply BOTTOM-UP — the closest one wraps first.
def add_one(fn):
def inner(x):
return fn(x) + 1
return inner
def add_ten(fn):
def inner(x):
return fn(x) + 10
return inner
DECORATORS = [add_one, add_ten]
@DECORATORS[0]
@DECORATORS[1]
def number(x):
return x
print(number(5))