目录
- 核心思想:装饰器是什么?
- 从零开始:一步步构建一个装饰器
- 1 函数是“一等公民”
- 2 不带参数的简单装饰器
- 2.1 问题:丢失了原函数的元信息
- 3 使用
functools.wraps修复元信息
- 进阶:带参数的装饰器
- 高级用法:装饰类
- 实际应用场景
- 1 函数执行计时
- 2 权限校验
- 3 日志记录
- 4 缓存(记忆化)
- 内置装饰器
核心思想:装饰器是什么?
一句话总结:装饰器是一个接受一个函数作为参数,并返回一个新函数的函数。

它就像一个“包装器”,在不修改原函数代码的情况下,为原函数增加额外的功能,这种设计模式遵循了 开放/封闭原则(对扩展开放,对修改封闭)。
核心目的: 代码复用 和 关注点分离,你可以将通用的功能(如日志、计时、权限检查)抽离出来,写成装饰器,然后应用到任何需要这些功能的函数上。
从零开始:一步步构建一个装饰器
1 函数是“一等公民”
在理解装饰器之前,必须明白 Python 中函数的地位,Python 的函数是“一等公民”,这意味着:
- 函数可以像变量一样被赋值。
- 函数可以作为参数传递给其他函数。
- 函数可以作为其他函数的返回值。
这个特性是装饰器能够实现的基础。

def say_hello():
print("Hello!")
# 1. 函数可以赋值给变量
greet = say_hello
greet() # 输出: Hello!
# 2. 函数可以作为参数传递
def run_something(func):
print("准备运行函数...")
func()
print("函数运行完毕。")
run_something(say_hello) # 输出:
# 准备运行函数...
# Hello!
# 函数运行完毕。
2 不带参数的简单装饰器
我们想给 say_hello 函数增加一个功能:在它执行前后打印一些信息,我们不想修改 say_hello 的内部代码。
错误的做法: 直接修改 say_hello,这违反了封闭原则。
# 不好的做法
def say_hello():
print("正在执行 say_hello...")
print("Hello!")
正确的做法:使用装饰器
def my_decorator(func):
# 这是一个包装函数,它会包裹原函数
def wrapper():
print("函数执行前:做一些准备工作...")
func() # 调用原始的函数
print("函数执行后:做一些清理工作...")
return wrapper # 返回包装后的新函数
# 应用装饰器
@my_decorator
def say_hello():
print("Hello!")
# 现在调用 say_hello,实际上调用的是 my_decorator 返回的 wrapper 函数
say_hello()
执行流程分析:

- Python 看到
@my_decorator语法,会自动执行my_decorator(say_hello)。 say_hello函数作为参数func传递给my_decorator。my_decorator内部定义了wrapper函数,然后返回这个wrapper函数。- 这个返回的
wrapper函数,被重新赋值给了say_hello这个名字。 - 当你调用
say_hello()时,你实际上是在调用wrapper()。
输出结果:
函数执行前:做一些准备工作...
Hello!
函数执行后:做一些清理工作...
2.1 问题:丢失了原函数的元信息
当你使用装饰器后,原函数的 __name__、__doc__ 等元信息会丢失,取而代之的是包装函数的信息。
print(say_hello.__name__) # 输出: wrapper print(say_hello.__doc__) # 输出: None (wrapper 没有 docstring)
这在调试和文档生成时非常不方便。
3 使用 functools.wraps 修复元信息
Python 的 functools 模块提供了一个 wraps 装饰器,专门用来解决这个问题,它会将被装饰函数的元信息复制到包装函数上。
最佳实践:在写任何装饰器时,都应在内部函数上使用 @functools.wraps。
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper():
print("函数执行前:做一些准备工作...")
func()
print("函数执行后:做一些清理工作...")
return wrapper
@my_decorator
def say_hello():
"""这是一个简单的问候函数"""
print("Hello!")
print(say_hello.__name__) # 输出: say_hello
print(say_hello.__doc__) # 输出: 这是一个简单的问候函数
进阶:带参数的装饰器
我们希望装饰器本身可以接收参数,我们想自定义一个日志消息。
思考: 装饰器的语法 @decorator 等价于 func = decorator(func)。decorator 要接收参数,那么它返回的必须仍然是一个函数(这个函数再接收 func 作为参数)。
这会导致一个嵌套结构:
- 最外层函数接收装饰器的参数(如
log_message)。 - 中间层函数接收被装饰的函数(如
func)。 - 最内层函数(
wrapper)执行实际的包装逻辑。
import functools
import time
# 这个函数的参数是装饰器要传入的参数
def repeat(num_times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs): # *args, **kwargs 用于处理带参数的原函数
print(f"函数 {func.__name__} 将会被执行 {num_times} 次。")
for _ in range(num_times):
result = func(*args, **kwargs)
return result # 如果函数有返回值,wrapper 需要返回它
return wrapper
return decorator
# 应用带参数的装饰器
@repeat(num_times=3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
执行流程分析:
- Python 看到
@repeat(num_times=3),会先执行repeat(num_times=3)。 - 这会返回一个
decorator函数(这个decorator函数内部已经记住了num_times=3)。 - Python 会用
greet函数作为参数去调用这个返回的decorator函数。 decorator函数内部定义并返回了wrapper函数。wrapper函数被赋值给greet。
输出结果:
函数 greet 将会被执行 3 次。
Hello, Alice!
Hello, Alice!
Hello, Alice!
高级用法:装饰类
装饰器不仅可以装饰函数,还可以装饰类,当装饰器放在类定义前时,装饰器函数会接收这个类作为参数。
应用场景:
- 为类动态添加属性或方法。
- 修改类的初始化行为。
- 实现单例模式。
示例:为类添加一个属性
def add_description(cls):
cls.description = "这是一个被装饰器增强过的类"
return cls
@add_description
class MyClass:
def __init__(self, value):
self.value = value
obj = MyClass(100)
print(obj.description) # 输出: 这是一个被装饰器增强过的类
print(obj.value) # 输出: 100
实际应用场景
1 函数执行计时
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
print(f"函数 {func.__name__} 执行耗时: {end_time - start_time:.4f} 秒")
return result
return wrapper
@timer
def slow_function(seconds):
time.sleep(seconds)
slow_function(2)
# 输出: 函数 slow_function 执行耗时: 2.000x 秒
2 权限校验
import functools
def login_required(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if not user.is_authenticated:
print("错误:用户未登录,无法访问!")
return None
return func(user, *args, **kwargs)
return wrapper
class User:
def __init__(self, name, is_authenticated):
self.name = name
self.is_authenticated = is_authenticated
@login_required
def create_post(user, title):
print(f"用户 {user.name} 正在创建文章: {title}")
# 测试
admin = User("Admin", True)
guest = User("Guest", False)
create_post(admin, "我的第一篇博客") # 正常执行
create_post(guest, "一篇匿名文章") # 输出错误信息
```
#### 5.3 日志记录
```python
import logging
import functools
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def logger(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"准备调用函数: {func.__name__}")
result = func(*args, **kwargs)
logging.info(f"函数 {func.__name__} 执行完毕。")
return result
return wrapper
@logger
def process_data(data):
print(f"正在处理数据: {data}")
return "处理完成"
process_data("12345")
# 输出到日志文件/控制台:
# 2025-10-27 10:30:00,123 - INFO - 准备调用函数: process_data
# 正在处理数据: 12345
# 2025-10-27 10:30:00,125 - INFO - 函数 process_data 执行完毕。
```
#### 5.4 缓存(记忆化)
```python
import functools
def memoize(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
# 如果结果已经在缓存中,直接返回
if args in cache:
return cache[args]
# 否则,计算结果并存入缓存
result = func(*args)
cache[args] = result
return result
return wrapper
@memoize
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(35)) # 第一次计算,会比较慢
print(fibonacci(35)) # 第二次,直接从缓存返回,瞬间完成
```
---
### 6. 内置装饰器
Python 自身也提供了一些非常有用的内置装饰器。
* **`@property`**: 将一个方法变成一个属性,使得调用方式更自然(`obj.attr` 而不是 `obj.method()`)。
* **`@staticmethod`**: 将一个方法标记为静态方法,它不接收第一个隐式的参数(`self` 或 `cls`),可以直接通过类或实例调用。
* **`@classmethod`**: 将一个方法标记为类方法,它接收第一个参数为类本身(通常命名为 `cls`),而不是实例,常用于工厂方法或操作类本身而不是实例的方法。
**示例:`@property`, `@staticmethod`, `@classmethod`**
```python
class MyClass:
def __init__(self, value):
self._value = value
# 使用 @property 将方法变成属性
@property
def value(self):
"""这是一个 getter 方法"""
return self._value
@value.setter
def value(self, new_value):
"""这是一个 setter 方法"""
if new_value > 0:
self._value = new_value
else:
raise ValueError("值必须大于0")
# 使用 @staticmethod 定义静态方法
@staticmethod
def static_method_info():
print("这是一个静态方法,不依赖于类或实例。")
# 使用 @classmethod 定义类方法
@classmethod
def class_method_info(cls):
print(f"这是一个类方法,接收的类是: {cls.__name__}")
# 常用于工厂方法
return cls(0) # 返回一个初始化为0的实例
# 测试
obj = MyClass(10)
print(obj.value) # 通过 @property 访问
obj.value = 20 # 通过 @value.setter 修改
print(obj.value)
# obj.value = -5 # 会抛出 ValueError
MyClass.static_method_info() # 通过类调用
MyClass.class_method_info() # 通过类调用
```
---
### 7.
| 特性 | 描述 |
| :--- | :--- |
| **核心定义** | 一个返回函数的函数,用于“包装”另一个函数,为其增加功能。 |
| **核心语法** | `@decorator_name` 放在函数定义前。 |
| **工作原理** | `@decorator` 是 `my_function = decorator(my_function)` 的语法糖。 |
| **关键技巧** | 始终在内部包装函数上使用 `@functools.wraps` 来保留原函数的元数据。 |
| **带参数** | 需要三层嵌套:外层接收装饰器参数,中层接收函数,内层执行逻辑。 |
| **应用场景** | 计时、日志、权限校验、缓存、单例模式、属性访问控制等。 |
| **面向对象** | 也可用于装饰类,用于动态修改类的行为或属性。 |
装饰器是 Python 中非常强大和优雅的特性,掌握它能让你的代码更加模块化、可读性更强,并且易于维护。 