带参数的函数装饰器详解
from functools import wraps
# A decorator that takes arguments is a decorator factory:
def repeat(n=1): # (A) factory: gets CONFIG at decoration time
# Called once at decoration time.
# Receives your configuration (n=3).
# Returns the decorator function deco, closing over n.
print(f"[Decoration time - repeat] factory n={n}")
def deco(func): # (B) real decorator: gets FUNC at decoration time
# Immediately called by Python with the function being decorated (func = say_hello).
# Returns wrapper, which closes over both func and n.
# Desugared: say_hello = repeat(3)(say_hello) <---- 重点
print(f"[Decoration time - repeat] deco func={func.__name__}")
@wraps(func)
def wrapper(*args, **kwargs): # (C) wrapper: gets CALL ARGS at call time
# Called every time you call say_hello(...) at runtime.
# Receives the original call’s positional/keyword args
print(f"[repeat] wrapper args={args}, kwargs={kwargs}")
result = None
for i in range(n):
print(f" calling {func.__name__} #{i+1}")
result = func(*args, **kwargs)
return result
return wrapper
return deco
@repeat(n=3) # ← runs (A) first; returns (B); then Python runs (B)(say_hello)
def say_hello(name):
print(f"hello {name}")
if __name__ == "__main__":
"""
There are two phases for decorators:
1. Decoration time (when Python executes the @... line during function definition)
2. Call time (when you later call the decorated function)
流程:
config → factory → decorator → wrapper
n → repeat → deco(func) → wrapper(*args, **kwargs)
"""
print("-----------Main Starts--------------")
say_hello("WORLD")
输出结果
[Decoration time - repeat] factory n=3
[Decoration time - repeat] deco func=say_hello
-----------Main Starts--------------
[repeat] wrapper args=('WORLD',), kwargs={}
calling say_hello #1
hello WORLD
calling say_hello #2
hello WORLD
calling say_hello #3
hello WORLD
-----------------------------------
Calling do_something with (<__main__.LearnDecorator object at 0x00000210487B1940>, 1, 'one') and {'x': 2}
do_something ended with 10
do_something got return: 10
可选带参数的函数装饰器详解
在之前的基础上,我们稍微加大难度,让decorator的参数optional。
from functools import wraps
from typing import Callable, Optional
def log(_func: Optional[Callable] = None, *, prefix: str = "[LOG]"):
"""
If used as @log:
- When you write @log on a function, Python calls the decorator with the function as a single positional argument
- _func = log(_func) # one positional arg: the function object; prefix uses default.
If used as @log(prefix="X"):
- _func is None, we return a decorator that knows prefix="X".
参数列表为什么会出现 “*, prefix=”
The * forces prefix to be keyword-only, preventing binding the function object to the wrong parameter.
比如,第一种情况
def bad_log(prefix='[LOG]', _func=None): # prefix is positional
...
@bad_log
def f(x): ...
这时,f = bad_log(f),f (the function) binds to prefix!
第二种情况
def log_no_star(_func=None, prefix='[LOG]'):
...
@log_no_star("X")
def g(y): ...
这时,用户想要设X给prefix,但是"X" binds to _func, not prefix → TypeError
"""
def deco(func: Callable):
print(f"[Decoration time - log] deco func={func.__name__}, prefix={prefix}")
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{prefix} calling {func.__name__} args={args} kwargs={kwargs}")
return func(*args, **kwargs)
return wrapper
if _func is not None:
# Case A: @log → desugars to func = log(func) <---- 重点
# Call deco immediately with the function and return the wrapper.
return deco(_func)
# Case B: @log(prefix="X") → desugars to func = log(prefix="X")(func) <---- 重点
# Return the decorator; Python will call it later with the function.
return deco
@log
def a(x): return x
@log(prefix="[LOG-Prefix]")
def b(y): return y
if __name__ == "__main__":
"""
There are two phases for decorators:
1. Decoration time (when Python executes the @... line during function definition)
2. Call time (when you later call the decorated function)
"""
print("-----------Main Starts--------------")
a(1)
b(1)
输出结果
[Decoration time - log] deco func=a, prefix=[LOG]
[Decoration time - log] deco func=b, prefix=[LOG-Prefix]
-----------Main Starts--------------
[LOG] calling a args=(1,) kwargs={}
[LOG-Prefix] calling b args=(1,) kwargs={}

被折叠的 条评论
为什么被折叠?



