首页 > 解决方案 > 装饰实例方法/类方法的 Python 装饰器是否有可能知道函数将绑定到的类?

问题描述

对于顶级函数:

def wrap(f: Callable) -> Callable:
    # Some logic
    return f

当这样的函数用于装饰定义在class主体中的另一个函数时:

class SomeClass:
    @wrap
    # may also be a @classmethod
    def some_method(self, *args, **kwargs):
        pass

是否可以在wrap函数内以某种方式检查传入的方法(SomeClass.some_method在这种情况下)并检索 的类型对象SomeClass

我尝试在运行时使用调试器进行检查f,但我可以从f包装器中找到的唯一相关信息是__qualname__包含类名的属性。


如果您想知道我为什么要这样做:我正在尝试为某个类创建某种基于方法名称(方法都是属性)的模式,并将该模式​​存储dict在我希望键是类对象本身。以类型签名表示:

SchemaSource = TypeVar('SchemaSource', bound=Any)
Method = Callable[..., Any]  # will be bound to SchemaSource
Schema = Dict[Type[SchemaSource], Dict[str, Method]]

当然,我可以__dict__稍后检查这个类,或者使用例如一个__init_subclass__钩子,但是因为我想在模式中包含一些方法,所以我认为装饰函数是通过单一来源提供这些信息的好方法。

标签: pythonpython-3.xdecoratordescriptor

解决方案


正如jasonharper 所提到的,在调用装饰器时该类还不存在,因此它接收的函数只是一个常规函数(除了它的名称提到了它将绑定到的类)。


对于我的问题,我最终采用了它attrs的风格,也使用了一个额外的装饰器来装饰类。

def include(f: Callable) -> Callable:
    """Add function `f` to SchemaBuilder."""
    SchemaBuilder.append(f)
    return f

class SchemaBuilder:
    records: Dict[Type, Dict[Callable, Any]] = {}
    temporary: List[Callable] = []

    @classmethod
    def append(cls, f: Callable):
        """Temporarily store the method in a list."""
        cls.temporary.append(f)

    @classmethod
    def register(cls, target_cls: Type):
        """Associate all methods stored in the list with `target_cls`.

        We rely on the fact that `target_cls` will be instantiated
        (and thus this method called) as soon as all of its (immediate)
        methods have been created.
        """
        cls.records[target_cls] = {k: None for k in cls.temporary}
        cls.temporary = []

# In use:

@SchemaBuilder.register  # called later
class SomeClass:
    @property
    @include  # called first
    def some_property(self):  # will be included
        pass

    @property
    def some_other_property(self):  # will not be included
        pass

推荐阅读