Patching\uuu init\u子类__

2024-06-30 15:00:12 发布

您现在位置:Python中文网/ 问答频道 /正文

我在修补自定义类“^{”时遇到问题。我认为这与我将补丁函数绑定到类的方式有关:

def _patched_initsubclass(cls, **kwargs):
    print(f"CLS from subclassing A: {cls}")
    super(cls, cls).__init_subclass__(**kwargs)

class A: ...

A.__init_subclass__ = _patched_initsubclass.__get__(A, A)

class B(A): ...  # Output: CLS from subclassing A: <class '__main__.A'>

但是,我知道正确设置的__init_subclass__应该有不同的输出:

class F:

    def __init_subclass__(cls, **kwargs):
        print(f"CLS from subclassing F: {cls}")
        pass

class C(F): ...  # Output: CLS from subclassing F: <class '__main__.C'>

cls在超类中__init_subclass__定义应该是子类时的子类。我试图通过不同的SO帖子和docs找到绑定dunder方法的正确方法,但是没有找到正确的方法


Tags: 方法fromoutputinitmaindefkwargsclass
2条回答

您对super的使用无效;它应该被传递它被调用的类的类型(例如,它在中定义的类)和它被传递的实际类型(它被调用的类),所以super(cls, cls)是谎言;显式使用描述符协议函数__get__将其预先绑定到A(在B上调用时绕过描述符协议),因此它总是说“我是从A调用的,带有A”,即使它实际上是在其他对象上调用的

你想要的东西不容易用正确的方法去做your approach of making it a ^{} (which means it actually gets ^{}, not ^{}, as expected) and calling ^{}仍然是错误的,即使它碰巧在这里工作。您告诉super遍历None对象的MRO,并调用它在MRO中B之后找到的第一个__init_subclass__。显然,即使B不在MRO中(这应该是一个错误)according to the docs:“如果第二个参数是一个对象,isinstance(obj, type)必须是真的。如果第二个参数是一个类型,issubclass(type2, type)必须是真的。“这是人生”,它会静静地返回object.__init_subclass__并调用它;它之所以有效,是因为object.__init_subclass__不做任何事情,也不反对被调用

正确执行此操作的唯一方法是为每个要修补的类制作一个新版本的_patched_initsubclass,该类知道它正在修补哪个类。另外,在执行此操作时,通过将__class__放在新方法的闭包范围内,您可以以启用零argsuper()的方式进行闭包(零argsuper()魔法是由编译器实现的,编译器使类中定义的所有函数在闭包范围内引用__class__super实际具有__class__可见的闭包).

一个例子是:

def make_patched_initsubclass_for(__class__):  # Receive class to patch as __class__ directly
    # Same as before, just defined inside function to get closure scope,
    # and super() is called with no arguments
    def _patched_initsubclass(cls, **kwargs):
        print(f"CLS from subclassing A: {cls}")
        super().__init_subclass__(**kwargs)    # super() roughly equivalent to super(__class__, cls)

    # Returns a classmethod so it descriptor protocol
    # knows to provide class uniformly, never an instance
    return classmethod(_patched_initsubclass)

class A: ...

A.__init_subclass__ = make_patched_initsubclass_for(A)  # Produces valid closure for binding to A

class B(A): ...  # Output CLS from subclassing A: <class '__main__.B'>

如果您没有将参数命名为make_patched_initsubclass_for{}(命名为patched_cls或类似的名称),则必须使用super(patched_cls, cls)而不是super(),但这两种方式都可以

我找到了一个解决方案,它不涉及通过__get__绑定路径函数:

def _patched_initsubclass(cls, **kwargs):
    print(f"CLS from subclassing A: {cls}")

    super(cls, None).__init_subclass__(**kwargs)


class A: ...


A.__init_subclass__ = classmethod(_patched_initsubclass)


class B(A): ...  # Output CLS from subclassing A: <class '__main__.B'>

我仍然不清楚为什么会这样:即classmethod()和直接与__get__绑定有什么区别

答案可能与classmethod在引擎盖下的作用有关,因此我将对此进行研究

我将把这个答案留给其他可能觉得有用的人,并将包括任何后续信息

相关问题 更多 >