当前位置:首页 > Python > 正文内容

Python 装饰器进阶:从原理到实战的完整指南

admin3周前 (03-22)Python23

Python 装饰器是 Python 语言中最强大、最优雅的特性之一。它允许你在不修改函数源代码的情况下,动态地为函数添加额外的功能。本文将从装饰器的基本原理出发,深入探讨带参数的装饰器、类装饰器、装饰器链以及实战应用场景。

一、装饰器的基本原理

装饰器的本质是一个接受函数作为参数,并返回一个新函数的高阶函数。Python 使用 @ 语法糖来让装饰器的使用更加简洁优雅。

让我们从一个最简单的计时装饰器开始:

import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f'函数 {func.__name__} 执行时间: {endend - start:.6f} 秒')
        return result
    return wrapper

@timer
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))

在这个例子中,timer 函数接受被装饰的函数作为参数,返回一个新的 wrapper 函数。当我们调用 fibonacci(10) 时,实际上是在调用 wrapper 函数,它会记录执行时间并调用原始的 fibonacci 函数。

注意:使用 functools.wraps 装饰器可以保留原函数的元信息(如 __name__、__doc__ 等),这对于调试和文档生成非常重要。

二、带参数的装饰器

有时候我们需要让装饰器接受参数,这时需要创建一个装饰器工厂函数。这个工厂函数接受参数,返回一个真正的装饰器。

def repeat(times=2):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                result = func(*args, **kwargs)
                results.append(result)
            return results[-1] if times > 0 else None
        return wrapper
    return decorator

@repeat(times=3)
def say_hello(name):
    print(f'你好, {name}!')
    return f'问候了 {name}'

say_hello('小明')

这个 repeat 装饰器可以让函数执行指定的次数。这种三层结构(工厂函数 → 装饰器 → wrapper)是带参数装饰器的标准模式。

三、类装饰器

除了函数装饰器,Python 还支持类装饰器。类装饰器必须实现 __call__ 方法,使其实例可以像函数一样被调用。

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
        functools.update_wrapper(self, func)
    
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f'调用次数: {self.count}')
        return self.func(*args, **kwargs)

@CountCalls
def process_data(data):
    print(f'处理数据: {data}')
    return data.upper()

print(process_data('hello'))
print(process_data('world'))
print(f'总调用次数: {process_data.count}')

类装饰器的优势是可以维护状态(如本例中的调用计数),这在某些场景下比函数装饰器更方便。

四、装饰器链

Python 允许一个函数应用多个装饰器,装饰器的执行顺序是从下到上(离函数定义最近的装饰器最先执行)。

def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f'调用函数: {func.__name__}')
        print(f'参数: args={args}, kwargs={kwargs}')
        result = func(*args, **kwargs)
        print(f'返回值: {result}')
        return result
    return wrapper

def cache_result(func):
    cache = {}
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        key = str(args) + str(kwargs)
        if key not in cache:
            cache[key] = func(*args, **kwargs)
            print('计算并缓存结果')
        else:
            print('从缓存中获取结果')
        return cache[key]
    return wrapper

@log_calls
@cache_result
def expensive_calculation(x, y):
    print('执行复杂计算...')
    time.sleep(0.1)
    return x ** 2 + y ** 2

print(expensive_calculation(3, 4))
print(expensive_calculation(3, 4))
print(expensive_calculation(5, 12))

在这个例子中,log_calls 会记录每次函数调用,而 cache_result 会缓存计算结果以提高性能。装饰器链让我们可以组合多个独立的功能模块。

五、实战应用:权限验证装饰器

在实际项目中,装饰器常用于权限验证、日志记录、性能监控等场景。下面是一个实用的权限验证装饰器:

class AuthenticationError(Exception):
    pass

def require_permission(permission):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if not user.get('authenticated', False):
                raise AuthenticationError('用户未登录')
            if permission not in user.get('permissions', []):
                raise AuthenticationError(f'缺少权限: {permission}')
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_permission('delete')
def delete_file(user, filename):
    print(f'删除文件: {filename}')
    return True

admin_user = {
    'authenticated': True,
    'permissions': ['read', 'write', 'delete']
}

guest_user = {
    'authenticated': True,
    'permissions': ['read']
}

delete_file(admin_user, 'test.txt')
try:
    delete_file(guest_user, 'test.txt')
except AuthenticationError as e:
    print(f'错误: {e}')

六、性能优化:装饰器实现缓存装饰器

对于纯函数(相同输入总是产生相同输出),我们可以使用装饰器实现缓存来大幅提升性能:

def memoize(max_size=128):
    def decorator(func):
        cache = {}
        order = []
        
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            key = (args, frozenset(kwargs.items()))
            
            if key in cache:
                order.remove(key)
                order.append(key)
                return cache[key]
            
            result = func(*args, **kwargs)
            
            if len(cache) >= max_size:
                oldest_key = order.pop(0)
                del cache[oldest_key]
            
            cache[key] = result
            order.append(key)
            return result
        
        wrapper.cache_info = lambda: {'hits': len(cache), 'size': max_size}
        return wrapper
    return decorator

@memoize(max_size=100)
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n-1)

import time
start = time.time()
print(factorial(50))
print(f'缓存信息: {factorial.cache_info()}')
print(f'执行时间: {time.time() - start:.6f} 秒')

七、总结

Python 装饰器是一种优雅而强大的代码复用机制,它遵循 DRY(Don't Repeat Yourself)原则,让我们的代码更加简洁和可维护。关键要点:

  • 装饰器本质是高阶函数,接受函数并返回新函数
  • 使用 functools.wraps 保留原函数的元信息
  • 带参数的装饰器需要三层结构(工厂 → 装饰器 → wrapper)
  • 类装饰器适合需要维护状态的场景
  • 装饰器链从下到上执行,可以组合多个功能
  • 实战中常用于权限验证、缓存、日志、性能监控等场景

掌握装饰器将让你的 Python 代码更加 Pythonic,更具表现力和可维护性。

相关文章

[Python 教程] Pandas 数据分析实战

Pandas 数据分析实战 Pandas 是 Python 数据分析的核心库,提供 DataFrame 和 Series 数据结构。本文介绍 Pandas 的实用技巧。 一、创建 DataFrame...

Python 上下文管理器的 5 个实用技巧,让你的代码更优雅

在 Python 编程中,上下文管理器(Context Manager)是一个优雅的资源管理工具。你可能已经熟悉最常见的用法——使用 with 语句打开文件,但上下文管理器的能力远不止于此。今天,我将...

Python 装饰器的高级应用与实战技巧

装饰器本质上是接受函数作为参数并返回新函数的高阶函数。理解这一点是掌握装饰器的关键。让我们从基础开始,逐步深入到高级应用。首先,我们需要理解函数在 Python 中是一等公民。这意味着函数可以像其他对...

Python 装饰器的 5 个实用场景:从入门到精通

装饰器(Decorator)是 Python 中的"函数包装器",它允许我们在不修改原函数代码的前提下,动态地添加功能。很多初学者学完 @decorator 语法后就止步不前,但实际上装饰器在实际工程...

Python 装饰器实战:从入门到精通

装饰器本质上是一个接受函数作为参数并返回新函数的高阶函数。理解这一点是掌握装饰器的关键。当你看到@decorator 语法时,Python 实际上是在执行 func = decorator(func)...

Python 中利用 functools.lru_cache 实现高效缓存:从入门到进阶

Python 中利用 functools.lru_cache 实现高效缓存:从入门到进阶 在日常 Python 开发中,我们经常会遇到重复计算相同输入的问题,比如递归计算斐波那契数列、多次调用相同参...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。