装饰器是 Python 中一个非常实用且常用的高级特性,它能够在不修改已有函数源代码和调用方式的前提下,为函数扩展额外的功能。本文将带你从基础概念出发,逐步理解装饰器的原理、写法、语法糖以及实际应用。

1. 什么是装饰器?

装饰器本质上是一个闭包函数,它接收一个函数作为参数,并返回一个新的函数(通常是内部函数)。这个新函数会在执行原函数的基础上,增加一些额外的功能。

装饰器的核心思想

  • 不修改已有函数的源代码
  • 不修改已有函数的调用方式
  • 通过"包装"的方式为函数增加功能

2. 从闭包到装饰器

2.1 闭包回顾

闭包是指在一个内部函数中引用了外部函数的变量,并且外部函数返回这个内部函数。装饰器是闭包的一种特殊形式,区别在于:

  • 闭包:外部函数接收的是普通变量
  • 装饰器:外部函数接收的是一个函数对象

2.2 基础装饰器写法

def decorator(func):
    """基础装饰器示例"""
    def inner():
        print("我是装饰器,我增加了新的功能")
        func()  # 执行原函数
    return inner

def comment():
    print("这是一个发表评论的功能")

# 使用装饰器
comment = decorator(comment)
comment()

输出:

我是装饰器,我增加了新的功能
这是一个发表评论的功能

3. 装饰器的工作原理

3.1 执行过程分析

  1. decorator(comment) 返回内部函数 inner
  2. comment = decorator(comment) 将原函数名重新指向 inner 函数
  3. 调用 comment() 实际上调用的是 inner(),其中包含了原函数的功能和新增功能

3.2 为什么不能直接修改原函数?

# ❌ 错误做法:直接修改源代码
def comment():
    print("我要增加新的功能")  # 直接添加代码
    print("这是一个发表评论的功能")

# ❌ 错误做法:改变调用方式
def comment():
    print("这是一个发表评论的功能")

def enhanced_comment():  # 创建新函数
    print("新增功能")
    comment()

这两种做法都不符合装饰器的设计理念,因为它们要么修改了源代码,要么改变了调用方式。

4. 装饰器语法糖

为了简化装饰器的使用,Python 提供了 @ 语法糖:

def decorator(func):
    def inner():
        print("我是装饰器,我增加了新的功能")
        func()
    return inner

@decorator  # 装饰器语法糖
def comment():
    print("这是一个发表评论的功能")

comment()

语法糖的执行原理
@decorator 等价于 comment = decorator(comment),只是写法更加简洁优雅。

5. 带参数的装饰器

5.1 装饰带参数的函数

def decorator(func):
    def inner(*args, **kwargs):
        print("函数执行前...")
        result = func(*args, **kwargs)
        print("函数执行后...")
        return result
    return inner

@decorator
def greet(name):
    print(f"Hello, {name}!")
    return f"Greeted {name}"

result = greet("Alice")
print(f"返回值: {result}")

5.2 带参数的装饰器

def repeat(times):
    """重复执行指定次数的装饰器"""
    def outer_wrapper(func):
        def inner(*args, **kwargs):
            results = []
            for i in range(times):
                print(f"第{i+1}次执行:")
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return inner
    return outer_wrapper

@repeat(times=3)
def say_hello(name):
    print(f"Hello, {name}!")
    return f"said hello to {name}"

say_hello("Bob")

6. 实际应用示例

6.1 计时装饰器

import time

def timer_decorator(func):
    """计时装饰器"""
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f"完成这个任务,消耗的时间为:{end_time - start_time:.6f}秒")
    return wrapper

@timer_decorator
def my_daily_work():
    """爱你三千遍功能"""
    for i in range(3000):
        print("I LOVE YOU")

my_daily_work()

6.2 权限验证装饰器

def require_login(func):
    """登录验证装饰器"""
    def wrapper(user, *args, **kwargs):
        if not user.get('is_authenticated', False):
            print("错误:用户未登录,请先登录!")
            return None
        return func(user, *args, **kwargs)
    return wrapper

@require_login
def view_profile(user):
    """查看用户资料"""
    print(f"用户名: {user['username']}")
    print(f"邮箱: {user['email']}")
    return user

# 测试
user1 = {'username': 'alice', 'email': 'alice@example.com', 'is_authenticated': True}
user2 = {'username': 'bob', 'email': 'bob@example.com', 'is_authenticated': False}

view_profile(user1)  # 正常执行
view_profile(user2)  # 提示未登录

7. 多个装饰器的执行顺序

def decorator1(func):
    def wrapper():
        print("装饰器1 - 前")
        func()
        print("装饰器1 - 后")
    return wrapper

def decorator2(func):
    def wrapper():
        print("装饰器2 - 前")
        func()
        print("装饰器2 - 后")
    return wrapper

@decorator1
@decorator2
def my_function():
    print("原始函数")

my_function()

输出:

装饰器1 - 前
装饰器2 - 前
原始函数
装饰器2 - 后
装饰器1 - 后

执行顺序:从下往上,从内到外。相当于 my_function = decorator1(decorator2(my_function))

8. 使用 functools.wraps 保留元信息

使用 functools.wraps 可以保留原函数的名称、文档字符串等元信息:

import functools

def my_decorator(func):
    @functools.wraps(func)  # 保留原函数的元信息
    def wrapper(*args, **kwargs):
        """包装函数"""
        print(f"调用函数: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def example():
    """这是一个示例函数"""
    return "Hello"

print(example.__name__)  # 输出: example(而不是 wrapper)
print(example.__doc__)   # 输出: 这是一个示例函数

9. 总结

装饰器是 Python 中非常强大的工具,它通过闭包的机制实现了对函数的"装饰"功能。掌握装饰器可以帮助你:

  1. 写出更加模块化和可复用的代码
  2. 实现横切关注点(如日志、权限、性能监控)的分离
  3. 保持代码的整洁和可维护性
  4. 遵循开放-封闭原则(对扩展开放,对修改封闭)

记住装饰器的核心:不修改源代码,不改变调用方式,只增加功能。通过合理使用装饰器,你可以大幅提升代码的质量和开发效率。


扩展阅读建议

  1. 深入学习 functools 模块的其他装饰器(如 lru_cache
  2. 了解 Python 内置装饰器(如 @classmethod, @staticmethod, @property
  3. 探索装饰器在 Web 框架(如 Flask、Django)中的应用
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐