我在修补自定义类‘__init_subclass__’时遇到了麻烦。我认为这与我将修补的函数绑定到类的方式有关:
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__应该是子类时的子类。我试图通过不同的所以posts和文档找到正确的方法来绑定dunder,但是没有找到正确的方法来实现它。
发布于 2020-12-08 21:37:02
您对super的使用是无效的;它应该传递正在调用它的类的类型(例如在其中定义的类)和传递的实际类型(调用它的类),所以super(cls, cls)是在撒谎;您明确使用描述符协议函数__get__将它预先绑定到A (当它在B上被调用时绕过描述符协议),所以它总是说“我是用一个A从A调用的”,即使它实际上是在其他事情上被调用的。
你想要的并不容易做正确的方式;super(cls, None),仍然是错误的,即使它碰巧在这里工作。您告诉super遍历None对象的MRO,并调用它在MRO中的B之后找到的第一个__init_subclass__。显然,尽管B不在MRO中(这应该是一个错误根据医生的说法:“如果第二个参数是对象,则isinstance(obj, type)必须是真。如果第二个参数是类型,则issubclass(type2, type)必须是真。”;c‘’est la vie),它默默地返回object.__init_subclass__并调用它;它的工作只因为object.__init_subclass__什么都不做,也不反对被调用。
正确做到这一点的唯一方法是为每个类创建一个新版本的_patched_initsubclass,以修补它正在修补的类。此外,在这样做时,您可以通过在新方法的闭包范围中放置super()来启用零arg __class__ (零arg super()魔术是通过编译器实现的,在引用__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或somesuch),则必须使用super(patched_cls, cls)而不是super(),但这两种方法都会起作用。
发布于 2020-12-08 21:13:13
我找到了一个不涉及通过__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在幕后做什么有关,所以我会研究这个问题。
我会留下这个答案给其他任何人,可能会发现它有帮助,并将包括任何后续信息。
https://stackoverflow.com/questions/65205205
复制相似问题