[英]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
上调用它时绕过描述符协议),所以它总是说“我是用A
从A
调用的”,即使它实际上是在其他东西上调用的。
你想要的并不容易以正确的方式去做; 您将其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.