修补 __init_subclass__

Patching __init_subclass__

我在修补自定义 class' __init_subclass__ 时遇到问题。我认为这与我将修补函数绑定到 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__ 应该有不同的输出:

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 在 super class' __init_subclass__ 定义中应该是 subclass 当 subclassed 时。我试图通过不同的 SO 帖子和 docs 找到绑定 dunder 方法的正确方法,但未能找到正确的方法。

我找到了一个不涉及通过 __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 的幕后工作有关,所以我会研究一下。

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

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

你想要的,做对的方法并不容易; , is still wrong, even if it happens to work by coincidence here. You're telling super to traverse the MRO of the None object and call the first __init_subclass__ it finds after B in the MRO. Apparently, even though B isn't in the MRO (which should be an error according to the docs: "如果第二个参数是对象,isinstance(obj, type) 必须为真。如果第二个参数是类型,issubclass(type2, type) 必须为真。"; c'est la vie),它默默地 returns object.__init_subclass__ 并调用它;它之所以有效,只是因为 object.__init_subclass__ 什么都不做,也不反对被调用。

正确执行此操作的唯一方法是为每个 class 制作一个新版本的 _patched_initsubclass 来修补知道它正在修补哪个 class。奖励,在执行此操作时,您可以通过将 __class__ 放入新方法的闭包范围(零参数 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(),但无论哪种方式都可以。