Python装饰器详解:从基础到进阶
装饰器是Python中一个强大且常用的特性,它允许在不修改函数或类源码的情况下,动态地增加功能。无论是日志记录、性能计时、权限校验还是缓存,装饰器都能让代码更加简洁优雅。本文将带你从基础语法出发,逐步深入装饰器的进阶用法和实际应用。
---
### 一、什么是装饰器?
装饰器本质上是一个可调用对象(通常是函数),它接收一个函数作为参数,并返回一个新的函数(或原函数)。使用 `@` 语法糖可以将装饰器应用到目标函数上。
一个最简单的装饰器:
```python
def my_decorator(func):
def wrapper():
print("函数执行前")
func()
print("函数执行后")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
# 输出:
# 函数执行前
# Hello!
# 函数执行后
```
等价于 `say_hello = my_decorator(say_hello)`。注意 `wrapper` 内部调用了原始的 `func`,但返回的是新的 `wrapper` 函数。
---
### 二、基础用法:处理带参数的函数
上面的装饰器只能用于无参函数。要支持任意参数,可以使用 `*args` 和 `**kwargs`:
```python
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__},参数: args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} 返回: {result}")
return result
return wrapper
@log_decorator
def add(a, b):
return a + b
add(3, 5)
# 输出:
# 调用 add,参数: args=(3, 5), kwargs={}
# add 返回: 8
```
这里 `wrapper` 接收任意参数并原样传递给 `func`,同时返回 `func` 的结果。
---
### 三、带参数的装饰器
有时我们需要装饰器本身接受参数,例如 `@repeat(3)` 让函数执行3次。实现方式是写一个返回装饰器的函数(三层嵌套):
```python
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
# 输出三次 Hello, Alice!
```
`repeat(3)` 返回 `decorator`,然后 `decorator` 被应用到 `greet` 上。理解三层结构的关键:最外层接收装饰器参数,中间层接收被装饰函数,最内层是实际执行逻辑。
---
### 四、类装饰器
除了函数,类也可以作为装饰器。类装饰器需要实现 `__call__` 方法:
```python
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"第 {self.count} 次调用 {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def say_hi():
print("Hi!")
say_hi()
say_hi()
# 输出:
# 第 1 次调用 say_hi
# Hi!
# 第 2 次调用 say_hi
# Hi!
```
类装饰器适合需要维护状态的场景(如计数器、缓存)。`__init__` 接收被装饰函数,`__call__` 实现调用逻辑。
---
### 五、使用 `functools.wraps` 保留元信息
直接使用装饰器后,原函数的 `__name__`、`__doc__` 等元信息会被 `wrapper` 覆盖。例如:
```python
@log_decorator
def add(a, b):
"""加法函数"""
return a + b
print(add.__name__) # 输出 'wrapper',而不是 'add'
```
解决方法是使用 `functools.wraps` 装饰 `wrapper`:
```python
from functools import wraps
def log_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# ... 逻辑 ...
return func(*args, **kwargs)
return wrapper
```
这样 `add.__name__` 和 `add.__doc__` 都会保留原函数的元信息。**强烈建议在所有装饰器中都使用 `@wraps(func)`**。
---
### 六、实际应用场景
#### 1. 性能计时
```python
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} 耗时 {elapsed:.4f} 秒")
return result
return wrapper
@timer
def slow_func():
time.sleep(1)
return "done"
```
#### 2. 权限校验(Web框架常见)
```python
def require_login(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if not user.get("is_authenticated"):
raise PermissionError("请先登录")
return func(user, *args, **kwargs)
return wrapper
```
#### 3. 缓存(LRU)
```python
from functools import lru_cache
@lru_cache(maxsize=128)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
```
`lru_cache` 是标准库中的装饰器,直接开箱即用。你也可以自己实现一个简单的缓存装饰器:
```python
def memoize(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
```
---
### 七、装饰器执行顺序与注意事项
多个装饰器可以叠加使用,执行顺序是**从下到上**(由内向外):
```python
@decorator1
@decorator2
def func():
pass
# 等价于 func = decorator1(decorator2(func))
```
因此 `decorator2` 先被应用,`decorator1` 后被应用。实际调用时,`decorator1` 的 wrapper 先执行,然后内部调用 `decorator2` 的 wrapper。
**注意事项**:
- 装饰器修改的是函数绑定,不是函数本身。原函数可以通过 `wrapper.__wrapped__` 访问(`functools.wraps` 会设置该属性)。
- 类的方法也可以被装饰,注意 `self` 会作为第一个参数传入 `wrapper`。
- 异步函数(`async def`)需要特殊的装饰器写法,或者使用支持协程的第三方库。
---
### 八、总结
装饰器是Python元编程的基石之一,它体现了“开放封闭原则”——对扩展开放,对修改封闭。掌握以下几点就能应付大多数场景:
- 用 `*args, **kwargs` 适配任意参数
- 用 `functools.wraps` 保留元信息
- 带参数的装饰器用三层嵌套
- 类装饰器通过 `__call__` 实现
- 多装饰器执行顺序由内向外
实际开发中,不要过度使用装饰器,保持代码可读性才是第一位的。希望本文能帮助你更自信地使用装饰器,写出更优雅的Python代码。
- 点赞
- 收藏
- 关注作者
评论(0)