You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改Python装饰器以支持类方法与实例方法?

问题描述

我写了一个Preloader类,用于在事件触发时将数据预加载至缓存,希望通过装饰器结合事件键注册加载函数,触发事件时执行这些函数。

代码实现如下:

from collections import defaultdict

class Preloader:
    def __init__(self):
        self.events = defaultdict(set)

    def register(self, event_key: str):
        print(f"Registering event: `{event_key}`")

        def decorator(func):
            print("Decorating function: ", func.__qualname__)

            def wrapper(*args, **kwargs):
                print("Wrapper called...")
                # 调用函数,将结果存入缓存并返回
                return func(*args, **kwargs)

            self.events[event_key].add(wrapper)
            return wrapper

        return decorator

    def preload(self, event_key: str, *args, **kwargs):
        for func in self.events[event_key]:
            func(*args, **kwargs)

随后用register装饰了以下函数:

preloader = Preloader()

class Product:

    @staticmethod
    @preloader.register("products")
    def get_products_static():
        # 实际场景为数据库调用
        print("Getting products from static method...")

    @classmethod
    @preloader.register("products")
    def get_products_cls(cls):
        # 实际场景为数据库调用
        print("Getting products from class method...")

调用preloader.preload('products')时,类方法调用失败,报错:

TypeError: Product.get_products_cls() missing 1 required positional argument: 'cls'

想知道如何修改装饰器,使其支持类方法与实例方法?


问题原因

装饰器执行顺序导致了问题:@classmethod/@staticmethod是在@preloader.register之后执行的,也就是说你装饰的是原始函数,而非经过方法绑定处理后的类方法/静态方法对象。直接调用原始函数时,不会自动传入cls(类方法)或忽略参数(静态方法),因此报错。


解决方案

修改Preloader类,让它能自动识别函数类型并处理方法绑定逻辑,无需调整装饰器顺序即可兼容普通函数、静态方法、类方法和实例方法:

from collections import defaultdict
import inspect
import sys

class Preloader:
    def __init__(self):
        self.events = defaultdict(list)

    def register(self, event_key: str):
        print(f"Registering event: `{event_key}`")

        def decorator(func):
            print("Decorating function: ", func.__qualname__)
            # 存储原始函数,不破坏原有装饰器逻辑
            self.events[event_key].append(func)
            return func  # 返回原始函数,让classmethod等装饰器正常工作

        return decorator

    def preload(self, event_key: str, *args, **kwargs):
        for func in self.events[event_key]:
            # 已绑定的方法(如通过实例调用的方法)直接执行
            if inspect.ismethod(func):
                func(*args, **kwargs)
                continue
            
            # 解析函数所属类信息
            qualname_parts = func.__qualname__.split('.')
            if len(qualname_parts) < 2:
                # 普通函数直接执行
                func(*args, **kwargs)
                continue
            
            # 获取函数所属的类
            cls_name = qualname_parts[-2]
            module = sys.modules[func.__module__]
            cls = getattr(module, cls_name, None)
            if not cls:
                func(*args, **kwargs)
                continue
            
            # 从类中获取经过装饰器处理后的方法对象
            class_attr = cls.__dict__.get(func.__name__)
            if isinstance(class_attr, classmethod):
                # 类方法:自动绑定类参数并执行
                class_attr.__get__(None, cls)(*args, **kwargs)
            elif isinstance(class_attr, staticmethod):
                # 静态方法:直接执行
                class_attr.__get__(None, cls)(*args, **kwargs)
            else:
                # 实例方法:需用户传入实例作为第一个参数
                func(*args, **kwargs)

测试验证

调用preloader.preload('products')后,会正常输出:

Getting products from static method...
Getting products from class method...

如果要支持实例方法,调用时传入实例即可:

class Product:
    @preloader.register("products")
    def get_products_instance(self):
        print(f"Getting products from instance {self}...")

# 调用实例方法
p = Product()
preloader.preload('products', p)

内容的提问来源于stack exchange,提问作者cmsommerville

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 06:07:04