Python编程精进:高效的装饰器

126 阅读3分钟

在编程领域,效率往往体现在我们能否优雅地复用和扩展代码。Python 装饰器是一种强大的工具,能够让开发者修改或扩展函数和方法的行为。然而,许多开发者仍然没有充分利用或理解装饰器。

正如计算机科学家 Alan Perlis 著名的论断:

“一种不会改变你对编程思维方式的语言,不值得学习。”

当你真正理解装饰器时,它们确实可以改变你对代码结构的思考方式。本文将深入探讨五个自定义的 Python 装饰器,这些装饰器可以帮助你提升项目质量并简化编程体验。

问题:代码中的重复模式

在代码开发中,经常会遇到重复的模式。例如,日志记录、错误处理和性能监控等任务通常需要在多个函数中重复相同的代码块。这种重复会增加出现错误的风险,并使维护变得更加困难。

解决方案:Python 装饰器

装饰器提供了一种更简洁、更优雅的解决方案。通过在函数周围包装额外的功能,装饰器可以减少冗余,促进代码复用,并提高可读性。

1. 测量执行时间

有时,你需要跟踪某些函数的执行时间,尤其是在进行性能测试时。为不同函数重复编写计时逻辑会非常繁琐。

装饰器time_logger

import time

def time_logger(func):
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        print(f"Function '{func.__name__}' took {end_time - start_time:.4f} seconds.")
        return result
    return wrapper

使用方法

@time_logger
def process_data(n):
    time.sleep(n)  
    return f"Processed {n} seconds of data."

process_data(2)

2. 重试易出错的函数

假设你有一个容易出现瞬态错误的函数,比如 API 调用。与其每次都编写重试逻辑,不如使用装饰器。

装饰器retry

import time

def retry(retries=3, delay=1):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for attempt in range(retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Attempt {attempt + 1} failed: {e}")
                    time.sleep(delay)
            raise Exception(f"Function '{func.__name__}' failed after {retries} retries.")
        return wrapper
    return decorator

使用方法

@retry(retries=5, delay=2)
def fetch_data():
    import random
    if random.random() < 0.8:
        raise ValueError("Transient error!")
    return "Data fetched successfully."

print(fetch_data())

3. 强制类型检查

Python 的类型提示虽然很有用,但在运行时并不会强制执行。这个装饰器可以确保函数调用时使用正确的参数类型。

装饰器type_check

def type_check(*expected_types):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for arg, expected in zip(args, expected_types):
                if not isinstance(arg, expected):
                    raise TypeError(f"Expected {expected}, got {type(arg)}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

使用方法

@type_check(int, int)
def add(a, b):
    return a + b

print(add(2, 3))  # 正常工作
# print(add(2, "3"))  # 抛出 TypeError

4. 调试函数调用

调试过程中需要理解函数是如何以及为什么被调用的。调试装饰器可以记录输入参数和返回值,使调试过程更加容易。

装饰器debug

def debug(func):
    def wrapper(*args, **kwargs):
        print(f"Calling '{func.__name__}' with args: {args} kwargs: {kwargs}")
        result = func(*args, **kwargs)
        print(f"'{func.__name__}' returned: {result}")
        return result
    return wrapper

使用方法

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

5. 速率限制

如果某个函数(例如获取 API 数据)在特定时间范围内只能运行有限的次数,那么速率限制装饰器就显得非常有价值。

装饰器rate_limiter

import time

def rate_limiter(calls, period):
    def decorator(func):
        last_calls = []
        def wrapper(*args, **kwargs):
            nonlocal last_calls
            now = time.time()
            last_calls = [t for t in last_calls if now - t < period]
            if len(last_calls) >= calls:
                raise RuntimeError("Rate limit exceeded.")
            last_calls.append(now)
            return func(*args, **kwargs)
        return wrapper
    return decorator

使用方法

@rate_limiter(calls=2, period=5)
def fetch_data():
    print("Fetching data...")

fetch_data()
time.sleep(2)
fetch_data()
# fetch_data()  # 抛出 RuntimeError

总结

Python 装饰器提供了一种优雅的方式来增强和扩展代码的功能。它们不仅减少了冗余,还使代码库更加模块化和易读。

正如 Donald Knuth 曾经说过的:

“程序是给人读的,只是偶尔被计算机执行。”

装饰器体现了这一理念,使代码更加简洁、更易于维护。