Python 装饰器完全指南:从入门到实战的优雅编程技巧

在 Python 中,装饰器(Decorator)是一种强大的编程工具,它允许你在不修改原函数 / 类代码的前提下,动态增强其功能。无论是日志记录、性能计时、权限验证,还是缓存优化,装饰器都能让代码更简洁、复用性更强,是「面向切面编程(AOP)」的核心实现方式。

本文将从「基础原理→逐步实现→进阶用法→实战场景」,带你彻底掌握 Python 装饰器,即使是新手也能轻松上手。

一、装饰器的核心概念:为什么需要它?

1. 先看一个痛点场景

假设你已经写好了多个业务函数,现在需要给每个函数添加「执行日志」功能(打印函数名和执行时间):

# 原有业务函数
def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

def subtract(a, b):
    return a - b

如果直接修改函数代码,会导致两个问题:

  • 代码冗余:每个函数都要写重复的日志逻辑;
  • 侵入性强:破坏了原函数的纯粹性,后续想移除日志功能需要逐个修改。

2. 装饰器的解决方案

装饰器的核心思想是「包装器」:用一个函数 / 类去包裹原函数 / 类,在不改变其内部逻辑的前提下,添加额外功能

使用装饰器后,效果如下:

# 定义装饰器(日志功能)
def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"函数 {func.__name__} 开始执行...")
        result = func(*args, **kwargs)  # 调用原函数
        print(f"函数 {func.__name__} 执行结束")
        return result
    return wrapper

# 用 @ 语法糖应用装饰器
@log_decorator
def add(a, b):
    return a + b

@log_decorator
def multiply(a, b):
    return a * b

# 调用函数(无需修改调用方式)
print(add(2, 3))  # 自动打印日志,输出 5
print(multiply(2, 3))  # 自动打印日志,输出 6

优势

  • 无侵入:原函数代码完全不变;
  • 可复用:装饰器可直接应用于任意函数;
  • 灵活性高:随时可以添加 / 移除装饰器。

3. 装饰器的本质

装饰器本质是「高阶函数 + 函数嵌套 + 闭包」的组合:

  • 高阶函数:接收函数作为参数,或返回函数作为结果;
  • 函数嵌套:在函数内部定义另一个函数;
  • 闭包:内层函数可以访问外层函数的变量(如上述 func)。

二、基础入门:一步步实现装饰器

在写装饰器之前,必须先掌握 Python 的一个核心特性:函数是一等对象(可以作为参数、返回值,也可以赋值给变量)。

1. 铺垫:函数是一等对象

# 1. 函数可以赋值给变量
def hello():
    print("Hello, Decorator!")

greet = hello  # 变量 greet 指向 hello 函数
greet()  # 输出:Hello, Decorator!

# 2. 函数可以作为参数传递
def wrapper(func):
    func()  # 调用传入的函数

wrapper(hello)  # 输出:Hello, Decorator!

# 3. 函数可以作为返回值
def create_func():
    def inner():
        print("我是内部函数")
    return inner  # 返回内部函数

f = create_func()
f()  # 输出:我是内部函数

2. 第一步:手动实现简单装饰器

基于上述特性,我们手动实现一个「计时装饰器」(统计函数执行时间):

import time

# 1. 定义装饰器函数(接收原函数作为参数)
def timer_decorator(func):
    # 2. 定义嵌套函数(包裹原函数,添加额外功能)
    def wrapper():
        start_time = time.time()  # 额外功能:记录开始时间
        func()  # 调用原函数
        end_time = time.time()    # 额外功能:记录结束时间
        print(f"函数 {func.__name__} 执行耗时:{end_time - start_time:.4f} 秒")
    # 3. 返回嵌套函数(替换原函数)
    return wrapper

# 定义原函数
def slow_func():
    time.sleep(1)  # 模拟耗时操作
    print("耗时函数执行完成")

# 4. 手动应用装饰器(等价于 @timer_decorator)
slow_func = timer_decorator(slow_func)

# 调用函数
slow_func()
# 输出:
# 耗时函数执行完成
# 函数 slow_func 执行耗时:1.0005 秒

3. 第二步:使用语法糖 @(简化装饰器应用)

Python 提供了 @装饰器名 语法糖,让装饰器的应用更简洁,无需手动赋值:

import time

def timer_decorator(func):
    def wrapper():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f"执行耗时:{end_time - start_time:.4f} 秒")
    return wrapper

# 用 @ 语法糖应用装饰器(等价于 slow_func = timer_decorator(slow_func))
@timer_decorator
def slow_func():
    time.sleep(1)
    print("耗时函数执行完成")

slow_func()  # 效果和手动应用一致

4. 第三步:兼容带参数的函数

上述装饰器只能用于无参函数,若原函数有参数(如 add(a, b)),调用时会报错。解决方法是让嵌套函数 wrapper 接收任意参数:

import time

def timer_decorator(func):
    # 用 *args 接收位置参数,**kwargs 接收关键字参数
    def wrapper(*args, **kwargs):
        start_time = time.time()
        # 传递参数给原函数
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"函数 {func.__name__} 执行耗时:{end_time - start_time:.4f} 秒")
        return result  # 返回原函数的结果
    return wrapper

# 带参数的函数
@timer_decorator
def add(a, b):
    time.sleep(0.5)
    return a + b

# 调用带参数的函数
result = add(2, 3)
print(f"结果:{result}")
# 输出:
# 函数 add 执行耗时:0.5003 秒
# 结果:5

关键*args 和 **kwargs 可以接收任意数量、任意类型的参数,确保装饰器兼容所有函数。

5. 第四步:保留原函数的元信息

装饰器会替换原函数,导致原函数的元信息(如 __name____doc__)丢失:

@timer_decorator
def add(a, b):
    """计算两个数的和"""
    return a + b

print(add.__name__)  # 输出:wrapper(而非 add)
print(add.__doc__)   # 输出:None(而非 "计算两个数的和")

解决方案:使用 functools.wraps 装饰器,自动复制原函数的元信息:

import time
from functools import wraps  # 导入 wraps

def timer_decorator(func):
    @wraps(func)  # 关键:保留原函数元信息
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"函数 {func.__name__} 执行耗时:{end_time - start_time:.4f} 秒")
        return result
    return wrapper

@timer_decorator
def add(a, b):
    """计算两个数的和"""
    return a + b

print(add.__name__)  # 输出:add(正确)
print(add.__doc__)   # 输出:计算两个数的和(正确)

注意:这是装饰器的最佳实践,务必养成使用 @wraps(func) 的习惯。

三、进阶用法:带参数的装饰器

有时候我们需要让装饰器本身支持参数,比如「日志装饰器可配置是否打印时间戳」「权限装饰器可指定允许的角色」。

带参数的装饰器需要三层函数嵌套

  1. 最外层函数:接收装饰器的参数;
  2. 中间层函数:接收原函数;
  3. 最内层函数:包裹原函数的执行逻辑。

示例:带参数的日志装饰器

import time
from functools import wraps

# 最外层:接收装饰器参数(enable_timestamp:是否打印时间戳)
def log_decorator(enable_timestamp=True):
    # 中间层:接收原函数
    def decorator(func):
        @wraps(func)
        # 最内层:包裹执行逻辑
        def wrapper(*args, **kwargs):
            # 使用装饰器参数
            if enable_timestamp:
                timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
                print(f"[{timestamp}] 函数 {func.__name__} 开始执行...")
            else:
                print(f"函数 {func.__name__} 开始执行...")
            result = func(*args, **kwargs)
            print(f"函数 {func.__name__} 执行结束")
            return result
        return wrapper
    return decorator

# 应用装饰器(不传递参数,使用默认值 True)
@log_decorator()
def add(a, b):
    return a + b

# 应用装饰器(传递参数 enable_timestamp=False)
@log_decorator(enable_timestamp=False)
def multiply(a, b):
    return a * b

# 调用函数
print(add(2, 3))
# 输出:
# [2025-11-11 15:30:00] 函数 add 开始执行...
# 函数 add 执行结束
# 5

print(multiply(2, 3))
# 输出:
# 函数 multiply 开始执行...
# 函数 multiply 执行结束
# 6

执行流程

  1. @log_decorator(enable_timestamp=False) → 先执行最外层函数,返回中间层 decorator
  2. decorator(multiply) → 中间层接收原函数,返回内层 wrapper
  3. multiply = wrapper → 调用 multiply(2,3) 实际执行 wrapper(2,3)

四、高级用法:类装饰器

除了函数装饰器,Python 还支持「类装饰器」—— 用类来实现装饰器,适合需要维护状态的场景(如统计函数调用次数、缓存结果)。

类装饰器的核心是实现 __call__ 方法:当装饰后的函数被调用时,会自动执行 __call__ 方法。

示例 1:统计函数调用次数(带状态的装饰器)

from functools import wraps

class CountDecorator:
    def __init__(self, func):
        # 初始化时接收原函数
        self.func = func
        self.count = 0  # 维护状态:记录调用次数

    def __call__(self, *args, **kwargs):
        # 函数被调用时执行
        self.count += 1
        print(f"函数 {self.func.__name__} 已调用 {self.count} 次")
        result = self.func(*args, **kwargs)
        return result

# 应用类装饰器(无需加括号,因为 __init__ 直接接收 func)
@CountDecorator
def add(a, b):
    return a + b

# 多次调用
add(2, 3)  # 输出:函数 add 已调用 1 次 → 结果 5
add(4, 5)  # 输出:函数 add 已调用 2 次 → 结果 9
add(6, 7)  # 输出:函数 add 已调用 3 次 → 结果 13

示例 2:带参数的类装饰器

如果类装饰器需要接收参数,需在 __init__ 中先接收装饰器参数,再通过 __call__ 接收原函数:

from functools import wraps

class CacheDecorator:
    def __init__(self, expire=60):
        # 接收装饰器参数:缓存过期时间(默认 60 秒)
        self.expire = expire
        self.cache = {}  # 缓存存储:key=参数,value=(结果, 过期时间)

    def __call__(self, func):
        # 接收原函数,返回 wrapper
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 生成缓存 key(基于函数名和参数)
            key = (func.__name__, args, frozenset(kwargs.items()))
            now = time.time()

            # 检查缓存是否存在且未过期
            if key in self.cache:
                result, expire_time = self.cache[key]
                if now < expire_time:
                    print(f"命中缓存!返回缓存结果")
                    return result
                else:
                    print(f"缓存已过期,重新计算")

            # 缓存不存在/过期,执行原函数
            result = func(*args, **kwargs)
            # 存入缓存(设置过期时间)
            self.cache[key] = (result, now + self.expire)
            print(f"缓存已更新")
            return result
        return wrapper

# 应用带参数的类装饰器
@CacheDecorator(expire=30)  # 缓存 30 秒
def slow_func(a):
    time.sleep(1)  # 模拟耗时操作
    return a * 2

# 第一次调用:无缓存,耗时 1 秒
print(slow_func(2))  # 输出:缓存已更新 → 4
# 30 秒内第二次调用:命中缓存,瞬间返回
print(slow_func(2))  # 输出:命中缓存!返回缓存结果 → 4

五、多个装饰器的叠加使用

一个函数可以同时应用多个装饰器,执行顺序遵循「包裹顺序从下到上,执行顺序从上到下」。

示例:日志装饰器 + 计时装饰器

import time
from functools import wraps

# 日志装饰器
def log_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"【日志】函数 {func.__name__} 开始执行")
        result = func(*args, **kwargs)
        print(f"【日志】函数 {func.__name__} 执行结束")
        return result
    return wrapper

# 计时装饰器
def timer_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"【计时】执行耗时:{end - start:.4f} 秒")
        return result
    return wrapper

# 多个装饰器叠加(顺序:先 log,后 timer)
@timer_decorator  # 上层装饰器
@log_decorator   # 下层装饰器
def add(a, b):
    time.sleep(0.5)
    return a + b

# 调用函数
print(add(2, 3))

执行顺序解析:

  1. 装饰器应用顺序(包裹):@log_decorator 先包裹 add,再被 @timer_decorator 包裹 → 最终 add = timer_decorator(log_decorator(add))
  2. 函数执行顺序:timer_decorator 的 wrapper → log_decorator 的 wrapper → 原函数 add → 返回到 log_decorator → 返回到 timer_decorator

输出结果:

【计时】开始计时...
【日志】函数 add 开始执行
【日志】函数 add 执行结束
【计时】执行耗时:0.5003 秒
5

六、实战场景:装饰器的高频应用

装饰器在实际开发中应用广泛,以下是几个最常用的场景,直接复制即可使用。

1. 日志记录(含参数和异常捕获)

import time
import logging
from functools import wraps

# 配置日志
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")

def log_with_exception(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            logging.info(f"函数 {func.__name__} 调用,参数:args={args}, kwargs={kwargs}")
            result = func(*args, **kwargs)
            logging.info(f"函数 {func.__name__} 执行成功,返回值:{result}")
            return result
        except Exception as e:
            logging.error(f"函数 {func.__name__} 执行失败,错误:{str(e)}", exc_info=True)
            raise  # 重新抛出异常,不影响原逻辑
    return wrapper

@log_with_exception
def divide(a, b):
    return a / b

divide(10, 2)  # 日志记录成功
divide(10, 0)  # 日志记录异常,同时抛出 ZeroDivisionError

2. 权限验证(Web 开发常用)

from functools import wraps

# 模拟用户角色
current_user = {"name": "admin", "role": "admin"}

def permission_required(required_role):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 验证角色
            if current_user["role"] != required_role:
                raise PermissionError(f"权限不足!需要 {required_role} 角色")
            return func(*args, **kwargs)
        return wrapper
    return decorator

# 仅 admin 角色可执行
@permission_required("admin")
def delete_data():
    print("数据删除成功")

# 仅 user 角色可执行
@permission_required("user")
def view_data():
    print("数据查看成功")

delete_data()  # 执行成功
view_data()    # 执行成功(admin 角色可兼容 user?可根据需求修改逻辑)

3. 缓存优化(避免重复计算)

from functools import wraps
import time

def cache_decorator(expire=60):
    cache = {}
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            key = (func.__name__, args, frozenset(kwargs.items()))
            now = time.time()

            # 检查缓存
            if key in cache and now < cache[key][1]:
                return cache[key][0]

            # 计算并缓存
            result = func(*args, **kwargs)
            cache[key] = (result, now + expire)
            return result
        return wrapper
    return decorator

# 缓存 10 秒
@cache_decorator(expire=10)
def fibonacci(n):
    """计算斐波那契数列(递归方式,重复计算多)"""
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(30))  # 第一次计算耗时较长
print(fibonacci(30))  # 10 秒内命中缓存,瞬间返回

4. 函数重试(网络请求常用)

import time
from functools import wraps

def retry_decorator(max_retries=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            retries = 0
            while retries < max_retries:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    retries += 1
                    if retries == max_retries:
                        raise  # 达到最大重试次数,抛出异常
                    print(f"执行失败({e}),{delay} 秒后重试...(剩余 {max_retries - retries} 次)")
                    time.sleep(delay)
        return wrapper
    return decorator

# 最多重试 3 次,每次间隔 2 秒
@retry_decorator(max_retries=3, delay=2)
def fetch_data():
    """模拟网络请求,随机失败"""
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络连接失败")
    print("数据获取成功")

fetch_data()

七、避坑指南:装饰器的常见问题

1. 装饰器改变原函数元信息

问题:如前文所述,装饰器会导致原函数的 __name____doc__ 等元信息丢失。解决方案:必须使用 from functools import wraps 装饰 wrapper 函数。

2. 装饰器应用顺序错误

问题:多个装饰器叠加时,顺序不当导致功能异常(如先计时后日志,会包含日志打印时间)。解决方案:按「功能逻辑顺序」排列,如「日志 → 计时 → 权限」,确保内层装饰器先执行核心功能。

3. 装饰器参数传递错误

问题:带参数的装饰器忘记加括号(如 @log_decorator 而非 @log_decorator()),导致 func 接收的是装饰器参数而非原函数。解决方案:带参数的装饰器必须加括号(即使无参数也需 @decorator()),无参数的装饰器不加括号。

4. 类方法 / 静态方法的装饰器

问题:装饰类方法时,未考虑 self 参数,导致报错。解决方案:装饰器的 wrapper 函数需用 *args 接收 self,且类方法装饰器应放在 @classmethod 或 @staticmethod 下方:

class MyClass:
    @log_decorator  # 放在 @classmethod 下方
    @classmethod
    def class_method(cls):
        print("类方法")

    @log_decorator
    @staticmethod
    def static_method():
        print("静态方法")

八、总结

Python 装饰器是「优雅编程」的典范,其核心价值在于「无侵入式增强功能」和「代码复用」。

核心要点回顾:

  1. 基础结构:函数装饰器 = 高阶函数 + 嵌套函数 + 闭包,需用 @wraps 保留元信息;
  2. 进阶用法:带参数的装饰器需三层嵌套,类装饰器需实现 __call__ 方法;
  3. 执行顺序:多个装饰器「下包上执行」;
  4. 实战场景:日志、计时、权限、缓存、重试等,覆盖大多数开发需求。

学习建议:

  1. 从简单装饰器(如日志、计时)入手,理解函数嵌套和闭包;
  2. 手动实现每个实战场景的装饰器,加深记忆;
  3. 在项目中尝试应用,逐步替换重复代码,感受装饰器的简洁之美。

装饰器的灵活运用能让你的 Python 代码更简洁、更高效、更具可维护性。掌握它,你将迈入 Python 进阶开发者的行列!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐