Python中的元编程(1)-装饰器

元编程(Meta-Programming) 这个概念平时写CRUD可能不怎么用,如果需要造轮子给别人使用,或者提高程序的抽象层次以减少重复代码,往往绕不开这个概念。

元编程所谓的“元”即“meta-”这一词根的翻译,有超越的、更高的意思,也蕴含着改变的意味。例如下面这些词汇:

  • metaphysics 形而上学:meta+physics,超越物质科学的科学
  • metamorphism 变形;变性: meta+morph形状+ism,变形

元编程就是这样的一种超越编程的编程、改变编程的编程。元编程涵盖的范围其实很宽广,主流语言或多或少都具有一定的元编程能力。古老如C语言可以通过宏展开在编译期实现对程序的修改,虽然只是文本的展开,但是通过一些技巧也能实现复杂代码的生成。在较新的语言中,提供了更多的特性,比如kotlin中的注解、反射。

对于python来说,主要有以下几种实现元编程的方法:

  • 装饰器
  • 元类
  • 反射
  • 描述符
  • 动态代码执行

装饰器

在Python中,装饰器是一种特殊类型的函数,它可以修改其他函数的功能或行为。装饰器本质上是一个接受一个函数作为参数的函数,并返回一个新的函数。使用装饰器可以很方便地为已有函数添加功能,而无需修改原函数的代码。

简单装饰器

一个简单的装饰器通常看起来像这样:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Something is happening before the function is called.")
        result = func(*args, **kwargs)
        print("Something is happening after the function is called.")
        return result  # 返回原始函数的结果
    return wrapper

@my_decorator
def say_hello(name): 
	print(f"Hello, {name}!")

say_hello("Alice")

运行将会得到下面的输出:

Something is happening before the function is called.
Hello, Alice!
Something is happening after the function is called.

这种特性使得装饰器能很方便地实现日志记录、权限检查、参数检查、性能计时等需求。

带参数的装饰器

有时候有些功能很适合使用装饰器完成,但是又要去装饰器的行为有一定的变化,这时候可以通过带参数的装饰器实现。

带参数的装饰器本质上是一个装饰器工厂,通过不同参数产生不同的实际装饰器。通过这种方式,可以在装饰器中传递参数,从而根据不同的需求调整其行为。因此带参数的装饰器需要使用三层嵌套函数结构:装饰器工厂、实际装饰器和包装函数。

实现失败重试的装饰器示例:

import time
import random

def retry(max_attempts=3, delay=1):
    """
    重试装饰器
    :param max_attempts: 最大重试次数
    :param delay: 每次重试之间的延迟时间(秒)
    """
    def decorator(func):
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    print(f"尝试 {attempts}/{max_attempts} 失败,错误信息:{e}")
                    time.sleep(delay)
            # 如果重试次数用完仍未成功,抛出异常
            raise Exception(f"经过 {max_attempts} 次尝试后,函数仍未成功执行。")
        return wrapper
    return decorator

# 示例函数,模拟可能失败的操作
@retry(max_attempts=5, delay=2)
def risky_function():
    print("正在执行 risky_function...")
    if random.random() < 0.7:  # 70% 的概率失败
        raise ValueError("随机失败!")
    return "成功执行!"

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

类装饰器

类装饰器是 Python 中一种特殊的装饰器,它通过类来实现对函数或方法的装饰。与函数装饰器不同,类装饰器利用类的实例化和方法调用机制来增强被装饰对象的功能。类装饰器通常通过定义一个类,并在该类中实现 __call__ 方法来实现装饰器的行为。一个典型的类装饰器通常包含以下部分:

  1. 类定义:定义一个类,通常包含 __init__ 方法和 __call__ 方法。
  2. __init__ 方法:接收被装饰的函数或方法,并将其存储为类的属性。
  3. __call__ 方法:实现装饰逻辑,每次调用被装饰的函数时,都会执行这个方法。

一个简单的类装饰器:

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.call_count = 0

    def __call__(self, *args, **kwargs):
        self.call_count += 1
        print(f"函数 {self.func.__name__} 被调用了 {self.call_count} 次")
        return self.func(*args, **kwargs)

# 使用类装饰器
@CountCalls
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("Alice")
say_hello("Bob")
say_hello("Charlie")

类装饰器的优势:

  1. 状态管理:类装饰器可以维护状态(如调用次数、执行时间等),而函数装饰器通常需要通过闭包来实现类似功能。
  2. 灵活性:类装饰器可以通过继承和组合来扩展功能,而函数装饰器通常需要嵌套多层函数来实现复杂逻辑。
  3. 可读性:类装饰器的结构更清晰,尤其是对于复杂的逻辑,类的结构更容易理解和维护。

装饰器的其他使用技巧

1、装饰器链,可以将多个装饰器应用于同一个函数,这些装饰器会按照从外到内的顺序被调用。

2、装饰器也可以用于异步函数,注意使用await。

3、使用functools.wraps保留被装饰函数的元信息,可以让外部对原函数的变化没有感知。


Python中的元编程(1)-装饰器
https://www.xiaos.tech/archives/pythonzhong-de-yuan-bian-cheng-1--zhuang-shi-qi
作者
xiaoski
发布于
2025年04月06日
许可协议