第一次看见别人代码里写着 @login_required、@staticmethod、@app.route("/") 的时候,我整个人是懵的。这是啥?宏?注释?某种咒语?
后来才知道,这叫装饰器(Decorator)。它不是魔法,只是把"包装函数"这件事写得特别优雅。今天我们就把它从里到外拆开看一遍。
一、最朴素的场景:我想给函数加点东西
假设你写了一个函数 say_hello,现在想看看它每次跑多久。最直接的做法是改函数本身:
import time def say_hello(name): start = time.time() print(f"Hello, {name}!") time.sleep(0.5) # 假装在干活 end = time.time() print(f"{name} 耗时 {end - start:.2f}s") say_hello("chatoken")
能跑,但问题也明显:我想测时间的函数不止一个,难道每个都写一遍 start/end?这就开始重复了。
二、第一步:把"包装"抽出来
我们可以写一个 timer 函数,接受一个函数,返回一个"加了一层壳"的新函数:
import time def timer(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} 耗时 {end - start:.2f}s") return result return wrapper def say_hello(name): print(f"Hello, {name}!") time.sleep(0.5) # 手动"装饰" say_hello = timer(say_hello) say_hello("chatoken")
输出:
Hello, chatoken! say_hello 耗时 0.50s
看出来了吗?say_hello 本身没动,只是被"包了一层"。这就是装饰器的本质——接受一个函数,返回一个新函数。
三、第二步:用 @ 语法糖,Python 帮你做这件事
既然大家都这么写,Python 就给了个语法糖 @:
import time def timer(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} 耗时 {end - start:.2f}s") return result return wrapper @timer def say_hello(name): print(f"Hello, {name}!") time.sleep(0.5) say_hello("chatoken")
这段代码和第二步那段完全等价,只是看起来更"声明式"了:哦,say_hello 这个函数,是带计时的。
记住这句话: @decorator 就是 func = decorator(func) 的语法糖。没有别的魔法。
四、带参数的装饰器
有时候你想让装饰器"可配置",比如重复执行 N 次,或者设置超时时间。这时候再套一层:
import time from functools import wraps def repeat(times): # 这层接配置参数 def decorator(func): @wraps(func) # 关键!保留原函数元信息 def wrapper(*args, **kwargs): for i in range(times): print(f"--- 第 {i+1} 次 ---") func(*args, **kwargs) return wrapper return decorator @repeat(times=3) def greet(name): print(f"Hi, {name}!") greet("chatoken")
注意 @wraps(func)——它把原函数的 __name__、__doc__ 等元信息复制过来。否则你的函数名会变成 wrapper,日志、调试都会很尴尬。
五、实战:一个真正的"鉴权"装饰器
Web 开发里常见的 @login_required,其实就是这个套路:
from functools import wraps def login_required(func): @wraps(func) def wrapper(*args, **kwargs): user = get_current_user() # 假设从 session/token 取 if not user: raise PermissionError("请先登录") return func(*args, **kwargs) return wrapper @login_required def delete_user(user_id): # 删用户的危险操作 ... @login_required def view_dashboard(): ...
每个需要登录的函数,加一行 @login_required 就好,不用每个函数里都写一遍 if 判断。整洁、复用、好读。
六、几个容易踩的坑
坑 1:忘记 *args, **kwargs
被装饰的函数签名是多样的,你的 wrapper 一定要接 *args, **kwargs,否则会丢参数。
坑 2:忘记 return result
被装饰的函数如果原本 return 了值,你的 wrapper 不返回,调用方就拿不到结果。
坑 3:装饰类/静态方法时顺序错乱
在类里装饰方法时,记得 @functools.wraps 一定要放在最靠近函数的那一层,否则元信息会错。
七、写在最后
装饰器这个东西,第一眼"我懂",第二眼"我不懂",第三眼"原来这么简单"。
它本质就是"函数是一等公民"这一特性的优雅应用:函数可以当参数传,可以当返回值,可以被替换。理解了这件事,装饰器、偏函数、柯里化、pipeline,全都通了。
所以下次再看到 @xxx,别怕——它就是给你函数"加 buff"。
One-liner takeaway
装饰器 = 接受函数 + 返回新函数。@decorator 只是 func = decorator(func) 的语法糖。写装饰器别忘 *args, **kwargs、返回值、和 functools.wraps。