修补 __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()
,但无论哪种方式都可以。
我在修补自定义 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
" 即使它实际上是在其他东西上调用的。
你想要的,做对的方法并不容易; 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()
,但无论哪种方式都可以。