Python 装饰器:把函数包起来,但不把脑子绕进去
Python 里有一个很优雅、也很容易把初学者绕晕的东西:装饰器。
它看起来像这样:
@decorator
def hello():
print("Hello")
很多人第一次看到 @xxx 会以为这是某种神秘语法。其实装饰器的本质很简单:
装饰器就是一个“接收函数,并返回新函数”的函数。
换句话说,它可以在不修改原函数代码的情况下,给原函数增加额外功能。
从一个普通函数开始
先看一个普通函数:
def say_hello():
print("Hello, Python!")
say_hello()
输出:
Hello, Python!
现在假设我们想在函数执行前后打印一些日志:
def say_hello():
print("函数开始执行")
print("Hello, Python!")
print("函数执行结束")
这样当然可以,但问题是:如果有 10 个函数都要加日志呢?
一个个改,太累,也不优雅。
这时装饰器就派上用场了。
函数也可以当作参数
理解装饰器前,要先知道 Python 里的函数可以像普通变量一样传来传去:
def say_hello():
print("Hello")
def run_func(func):
func()
run_func(say_hello)
这里 say_hello 被当作参数传给了 run_func。
这就是装饰器成立的基础。
写一个最简单的装饰器
def log(func):
def wrapper():
print("函数开始执行")
func()
print("函数执行结束")
return wrapper
这个 log 函数接收一个函数 func,然后在内部定义了一个新函数 wrapper。
wrapper 做了三件事:
print("函数开始执行")
func()
print("函数执行结束")
最后,log 返回了这个 wrapper。
使用方式如下:
def say_hello():
print("Hello, Python!")
say_hello = log(say_hello)
say_hello()
输出:
函数开始执行
Hello, Python!
函数执行结束
这就是装饰器的本质。
@ 语法只是简写
上面的代码:
say_hello = log(say_hello)
可以简写成:
@log
def say_hello():
print("Hello, Python!")
完整代码:
def log(func):
def wrapper():
print("函数开始执行")
func()
print("函数执行结束")
return wrapper
@log
def say_hello():
print("Hello, Python!")
say_hello()
这两种写法完全等价。
所以:
@log
def say_hello():
...
本质上就是:
say_hello = log(say_hello)
处理带参数的函数
刚才的装饰器只能装饰没有参数的函数。
如果函数有参数:
def greet(name):
print(f"Hello, {name}")
原来的装饰器就会报错。
我们需要让 wrapper 接收任意参数:
def log(func):
def wrapper(*args, **kwargs):
print("函数开始执行")
result = func(*args, **kwargs)
print("函数执行结束")
return result
return wrapper
使用:
@log
def greet(name):
print(f"Hello, {name}")
greet("Mu-Miao")
输出:
函数开始执行
Hello, Mu-Miao
函数执行结束
这里的:
*args
**kwargs
表示可以接收任意位置参数和关键字参数。
这样装饰器就更通用了。
保留原函数信息
还有一个小问题。
看下面这段代码:
print(greet.__name__)
你可能希望输出:
greet
但实际可能输出:
wrapper
因为经过装饰后,greet 实际上已经变成了 wrapper。
为了解决这个问题,可以使用 functools.wraps:
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("函数开始执行")
result = func(*args, **kwargs)
print("函数执行结束")
return result
return wrapper
这样就能保留原函数的名字、文档字符串等信息。
实际开发中,写装饰器时建议加上 @wraps(func)。
一个实用例子:统计函数运行时间
装饰器常用于日志、权限校验、缓存、性能统计等场景。
比如统计函数运行耗时:
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 执行耗时:{end - start:.4f} 秒")
return result
return wrapper
使用:
@timer
def slow_task():
time.sleep(1)
print("任务完成")
slow_task()
输出:
任务完成
slow_task 执行耗时:1.0012 秒
这个例子里,slow_task 本身只负责做任务,计时逻辑由装饰器负责。
这就是装饰器的好处:把额外逻辑和核心逻辑分开。
带参数的装饰器
有时候,我们希望装饰器本身也能接收参数。
比如:
@repeat(3)
def say_hi():
print("Hi")
希望函数执行 3 次。
这时需要多包一层函数:
from functools import wraps
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
使用:
@repeat(3)
def say_hi():
print("Hi")
say_hi()
输出:
Hi
Hi
Hi
这里有三层函数:
repeat(times)
decorator(func)
wrapper(*args, **kwargs)
可以这样理解:
repeat(3)接收装饰器参数decorator(func)接收被装饰的函数wrapper()执行增强后的逻辑
装饰器常见用途
装饰器在实际开发中非常常见,比如:
@login_required
def user_profile():
...
表示访问这个函数前需要登录。
@cache
def get_data():
...
表示缓存函数结果。
@timer
def train_model():
...
表示统计函数执行时间。
在 Flask、FastAPI、Django 等框架里,装饰器也经常出现:
@app.route("/hello")
def hello():
return "Hello"
这里的 @app.route("/hello") 本质上也是一个装饰器。
总结
Python 装饰器看起来神秘,但核心思想并不复杂。
一句话总结:
装饰器是一个接收函数、返回函数的函数,用来在不修改原函数代码的情况下增强函数功能。
它的基本形式是:
def decorator(func):
def wrapper(*args, **kwargs):
# 执行前的逻辑
result = func(*args, **kwargs)
# 执行后的逻辑
return result
return wrapper
再配合 @decorator 语法,就可以写出非常简洁、优雅、可复用的代码。
刚开始学装饰器时,不用急着记住所有复杂写法。先记住这句话就够了:
@log
def func():
...
等价于:
func = log(func)
理解了这一点,装饰器就不再是魔法,而只是 Python 给函数穿上的一件外套。

