繁体   English   中英

修补 __init_subclass__

[英]Patching __init_subclass__

我在修补自定义类__init_subclass__时遇到问题。 我认为这与我将修补的 function 绑定到 class 的方式有关:

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__应该有不同的 output:

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'>

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

您对super的使用无效; 它应该传递被调用的 class 的类型(例如,它被定义的 class)和它传递的实际类型( super(cls, cls) ,它被称为撒谎); 您明确使用描述符协议 function __get__将其预先绑定到A (在B上调用它时绕过描述符协议),所以它总是说“我是用AA调用的”,即使它实际上是在其他东西上调用的。

你想要的并不容易以正确的方式去做; 您将其classmethod (这意味着它实际上得到B ,而不是A ,正如预期的那样)并调用super(cls, None)的方法仍然是错误的,即使它碰巧在这里起作用。 你告诉super遍历None object 的 MRO 并调用它在 MRO 中B之后找到的第一个__init_subclass__ 显然,即使B不在 MRO 中(根据文档,这应该是一个错误:“如果第二个参数是 object, isinstance(obj, type)必须为真。如果第二个参数是一个类型, issubclass(type2, type)必须为真。"; c'est la vie),它默默地返回object.__init_subclass__并调用它; 它之所以有效,只是因为object.__init_subclass__没有做任何事情,也没有 object 被调用。

正确执行此操作的唯一方法是为每个 class 创建一个新版本的_patched_initsubclass子类,以修补知道它正在修补哪个 class。 奖励,在执行此操作时,您可以通过将__class__放入闭包 scope 中以启用零参数super()的方式创建闭包,以实现新方法(零参数super()魔术是由编译器完成定义的所有函数在 class 中引用__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 __class__ (将其命名为patched_cls或类似名称),则必须使用super(patched_cls, cls)而不是super() ,但无论哪种方式都可以。

我找到了一个不涉及通过__get__绑定路径 function 的解决方案:

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在幕后的作用有关,所以我会研究一下。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM