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

Python 装饰器的 5 个实用技巧,让你的代码更优雅

admin3小时前Python2

在 Python 编程中,装饰器(Decorator)是一个强大而优雅的工具。很多初学者对装饰器的理解停留在@staticmethod 或@classmethod 这类内置装饰器上,但实际上,自定义装饰器能够极大地提升代码的可读性和可维护性。

本文将通过 5 个实用的装饰器技巧,带你深入理解装饰器的工作原理,并学会如何在实际项目中灵活运用。所有代码示例都是原创编写,可以直接在你的项目中使用。

技巧一:带参数的装饰器

最基础的装饰器不带参数,但实际工作中我们经常需要传递参数给装饰器。下面是一个可以重复执行函数的装饰器:

import functools
from typing import Callable

def repeat(times: int):
    """让函数重复执行指定次数的装饰器"""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for i in range(times):
                print(f"第 {i + 1}/{times} 次执行")
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator

# 使用示例
@repeat(times=3)
def greet(name: str) -> str:
    return f"Hello, {name}!"

# 调用
results = greet("World")
print(results)  # ['Hello, World!', 'Hello, World!', 'Hello, World!']

这个装饰器的关键在于多层嵌套:最外层接收参数,中间层接收函数,最内层执行实际逻辑。使用 functools.wraps 可以保留原函数的元信息。

技巧二:计时装饰器

性能分析是开发中的常见需求。下面这个装饰器可以测量函数的执行时间:

import functools
import time
from typing import Callable

def timer(unit: str = "seconds"):
    """测量函数执行时间的装饰器"""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            result = func(*args, **kwargs)
            end = time.perf_counter()
            elapsed = end - start
            
            if unit == "milliseconds":
                elapsed *= 1000
                unit_symbol = "ms"
            elif unit == "microseconds":
                elapsed *= 1000000
                unit_symbol = "μs"
            else:
                unit_symbol = "s"
            
            print(f"{func.__name__} 执行时间:{elapsed:.4f} {unit_symbol}")
            return result
        return wrapper
    return decorator

# 使用示例
@timer(unit="milliseconds")
def slow_function():
    time.sleep(0.5)
    return "完成"

slow_function()  # 输出:slow_function 执行时间:500.xxxx ms

使用 time.perf_counter() 比 time.time() 更精确,适合性能测试。通过 unit 参数可以灵活选择时间单位。

技巧三:缓存装饰器(记忆化)

对于计算密集型且结果可预测的函数,缓存之前的计算结果可以显著提升性能:

import functools
from typing import Callable, Any, Dict, Tuple

def cache(maxsize: int = 128):
    """简单的 LRU 缓存装饰器"""
    def decorator(func: Callable) -> Callable:
        cache_store: Dict[Tuple, Any] = {}
        access_order = []
        
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 创建缓存键
            key = (args, tuple(sorted(kwargs.items())))
            
            if key in cache_store:
                print(f"[缓存命中] {func.__name__}")
                # 更新访问顺序
                access_order.remove(key)
                access_order.append(key)
                return cache_store[key]
            
            print(f"[缓存未命中] 计算 {func.__name__}")
            result = func(*args, **kwargs)
            
            # 存储结果
            if len(cache_store) >= maxsize:
                # 移除最久未使用的
                oldest_key = access_order.pop(0)
                del cache_store[oldest_key]
            
            cache_store[key] = result
            access_order.append(key)
            return result
        
        wrapper.cache_clear = lambda: cache_store.clear()
        wrapper.cache_info = lambda: f"缓存大小:{len(cache_store)}/{maxsize}"
        return wrapper
    return decorator

# 使用示例
@cache(maxsize=5)
def fibonacci(n: int) -> int:
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))  # 55
print(fibonacci(10))  # 缓存命中
print(fibonacci(8))   # 缓存命中

这个实现展示了 LRU(最近最少使用)缓存策略的核心思想。当缓存满时,移除最久未访问的元素。

技巧四:重试装饰器

网络请求或文件操作可能会临时失败,自动重试可以提高程序的健壮性:

import functools
import time
import random
from typing import Callable, Any, Type, Tuple

def retry(
    max_attempts: int = 3,
    delay: float = 1.0,
    backoff: float = 2.0,
    exceptions: Tuple[Type[Exception], ...] = (Exception,)
):
    """失败后自动重试的装饰器"""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            current_delay = delay
            last_exception = None
            
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    last_exception = e
                    if attempt < max_attempts:
                        print(f"尝试 {attempt}/{max_attempts} 失败:{e}")
                        print(f"等待 {current_delay:.1f} 秒后重试...")
                        time.sleep(current_delay)
                        current_delay *= backoff
                    else:
                        print(f"所有 {max_attempts} 次尝试均失败")
            
            raise last_exception
        return wrapper
    return decorator

# 使用示例
@retry(max_attempts=3, delay=0.5, backoff=2.0)
def unstable_operation():
    if random.random() < 0.7:  # 70% 概率失败
        raise ConnectionError("网络连接失败")
    return "操作成功"

try:
    result = unstable_operation()
    print(result)
except Exception as e:
    print(f"最终失败:{e}")

指数退避(exponential backoff)是重试策略中的经典模式,每次重试的等待时间成倍增长,避免对服务造成过大压力。

技巧五:权限验证装饰器

在 Web 应用或 CLI 工具中,经常需要验证用户权限。装饰器可以优雅地实现这一功能:

import functools
from typing import Callable, List, Any
from enum import Enum

class Role(Enum):
    GUEST = 0
    USER = 1
    ADMIN = 2

class User:
    def __init__(self, name: str, role: Role):
        self.name = name
        self.role = role

# 全局当前用户(实际项目中应从 session 获取)
_current_user: User = None

def set_current_user(user: User):
    global _current_user
    _current_user = user

def require_role(*allowed_roles: Role):
    """验证用户角色的装饰器"""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            if _current_user is None:
                raise PermissionError("未登录")
            
            if _current_user.role not in allowed_roles:
                role_names = ", ".join(r.name for r in allowed_roles)
                raise PermissionError(
                    f"权限不足:需要 [{role_names}],当前为 {_current_user.role.name}"
                )
            
            return func(*args, **kwargs)
        return wrapper
    return decorator

# 使用示例
@require_role(Role.ADMIN)
def delete_all_users():
    return "已删除所有用户"

@require_role(Role.USER, Role.ADMIN)
def view_profile():
    return f"当前用户:{_current_user.name}"

# 测试
set_current_user(User("张三", Role.USER))
print(view_profile())  # 成功
# delete_all_users()  # 抛出 PermissionError

这种模式在 Flask、Django 等框架中非常常见。通过装饰器,权限逻辑与业务逻辑完全分离,代码更加清晰。

总结

装饰器是 Python 中最优雅的特性之一。掌握这 5 个技巧后,你可以:

  • 减少重复代码,提高可维护性
  • 将横切关注点(日志、权限、缓存等)与业务逻辑分离
  • 编写更加 Pythonic 的代码

记住,好的装饰器应该:1)使用 functools.wraps 保留元信息;2)保持单一职责;3)有清晰的命名和文档。

希望这些技巧能帮助你在项目中写出更优雅的代码!如果你有其他的装饰器使用场景,欢迎在评论区分享。

相关文章

[Python 教程] OpenCV-Python 入门:图像处理基础详解

OpenCV-Python 入门:图像处理基础详解OpenCV 是一个跨平台计算机视觉库,轻量级且高效,支持 Python 接口。本文将系统介绍 OpenCV 的核心概念和基础操作。一、OpenCV...

[Python 教程] OpenCV 绘图教程:图形与文本标注

OpenCV 绘图教程:图形与文本标注本文介绍如何在 OpenCV 中绘制各种图形和添加文本,用于图像标注和可视化。一、绘制基本图形1.1 创建画布import cv2 import&nb...

[Python 教程] NumPy 数组操作详解

NumPy 数组操作详解 NumPy 是 Python 科学计算的基础库,提供高性能的多维数组对象。本文详细介绍 NumPy 数组的核心操作。 一、创建数组 import numpy as np...

[Python 教程] Matplotlib 数据可视化教程

Matplotlib 数据可视化教程 Matplotlib 是 Python 最常用的绘图库。本文介绍常用图表的绘制方法。 一、基础设置 import matplotlib.pyplot as pl...

[Python 教程] Python 多线程编程指南

Python 多线程编程指南 Python 的 threading 模块提供多线程支持。本文介绍多线程编程的基础和实用技巧。 一、创建线程 import threading import time...

[Python 教程] Python 网络请求与爬虫基础

Python 网络请求与爬虫基础 requests 是 Python 最常用的 HTTP 库。本文介绍网络请求和爬虫的基础知识。 一、基础请求 import requests # GET 请求 r...

发表评论

访客

看不清,换一张

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