杰瑞科技汇

Python装饰器如何使用?

目录

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

核心思想:装饰器是什么?

一句话总结:装饰器是一个接受一个函数作为参数,并返回一个新函数的函数。

Python装饰器如何使用?-图1
(图片来源网络,侵删)

它就像一个“包装器”,在不修改原函数代码的情况下,为原函数增加额外的功能,这种设计模式遵循了 开放/封闭原则(对扩展开放,对修改封闭)。

核心目的: 代码复用关注点分离,你可以将通用的功能(如日志、计时、权限检查)抽离出来,写成装饰器,然后应用到任何需要这些功能的函数上。


从零开始:一步步构建一个装饰器

1 函数是“一等公民”

在理解装饰器之前,必须明白 Python 中函数的地位,Python 的函数是“一等公民”,这意味着:

  • 函数可以像变量一样被赋值。
  • 函数可以作为参数传递给其他函数。
  • 函数可以作为其他函数的返回值。

这个特性是装饰器能够实现的基础。

Python装饰器如何使用?-图2
(图片来源网络,侵删)
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装饰器如何使用?-图3
(图片来源网络,侵删)
  1. Python 看到 @my_decorator 语法,会自动执行 my_decorator(say_hello)
  2. say_hello 函数作为参数 func 传递给 my_decorator
  3. my_decorator 内部定义了 wrapper 函数,然后返回这个 wrapper 函数。
  4. 这个返回的 wrapper 函数,被重新赋值给了 say_hello 这个名字。
  5. 当你调用 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 作为参数)。

这会导致一个嵌套结构:

  1. 最外层函数接收装饰器的参数(如 log_message)。
  2. 中间层函数接收被装饰的函数(如 func)。
  3. 最内层函数(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")

执行流程分析:

  1. Python 看到 @repeat(num_times=3),会先执行 repeat(num_times=3)
  2. 这会返回一个 decorator 函数(这个 decorator 函数内部已经记住了 num_times=3)。
  3. Python 会用 greet 函数作为参数去调用这个返回的 decorator 函数。
  4. decorator 函数内部定义并返回了 wrapper 函数。
  5. 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 中非常强大和优雅的特性,掌握它能让你的代码更加模块化、可读性更强,并且易于维护。
分享:
扫描分享到社交APP
上一篇
下一篇