动态 __init_subclass__ 方法的参数绑定
Posted
技术标签:
【中文标题】动态 __init_subclass__ 方法的参数绑定【英文标题】:argument binding for dynamic __init_subclass__ method 【发布时间】:2018-10-12 17:12:22 【问题描述】:我正在尝试让类装饰器工作。装饰器会将__init_subclass__
方法添加到它所应用到的类中。
但是,当方法被动态添加到类时,第一个参数不会绑定到子类对象。为什么会这样?
作为一个例子:这是可行的,下面的静态代码是我试图最终得到的一个例子:
class C1:
def __init_subclass__(subcls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
print(f"init_subclass -> subcls.__name__, args!r, kwargs!r")
测试:
>>> D = type("D", (C1,), )
init_subclass -> D, (),
但是,如果我动态添加 __init__subclass__
方法,则子类不会绑定到第一个参数:
def init_subclass(subcls, **kwargs):
super().__init_subclass__(**kwargs)
print(f"init_subclass -> subcls.__name__, args!r, kwargs!r")
def decorator(Cls):
Cls.__init_subclass__ = init_subclass
return Cls
@decorator
class C2:
pass
测试:
>>> D = type("D", (C2,), )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: init_subclass() missing 1 required positional argument: 'subcls'
为什么会发生这种情况,我怎样才能做到这一点并让绑定以正确的方式工作?
【问题讨论】:
【参考方案1】:__init_subclass__
is an implicit classmethod.
可能无法使用零参数 super(如果您想了解原因,请阅读 here),但您应该能够在装饰器本身内显式绑定 super。
def decorator(Cls):
def __init_subclass__(subcls, **kwargs):
print(f'init subclass Cls!r, subcls!r, kwargs!r')
super(Cls, subcls).__init_subclass__(**kwargs)
Cls.__init_subclass__ = classmethod(__init_subclass__)
return Cls
@decorator
class C:
pass
class D(C):
pass
【讨论】:
我想我有一个想法,但是你能解释一下解释器对super()
的subcls
参数的需求吗?没有它,我会收到运行时错误。参数不应该是C2
...吗?
嗯,是的——从技术上讲,它应该是super(C2, subcls)
,我很草率。当 Cls
尚不存在时,无参数 super 的工作方式(使用 __class__
单元格)使得这很棘手,但我认为您可以在装饰器中使用闭包,请参阅编辑并让我知道它是如何实现的去(未经测试)。
关闭效果很好。它甚至可以在使用exec
和一串代码构建时工作。【参考方案2】:
只是对那些提倡使用abc
的人的评论。虽然abc
也可以解决相关问题,但值得一提的是两种方法之间存在两个差异(据我所知):
类定义与实例化。
abc.abstractmethod
装饰器在类实例化时强制对子类进行约束,而 __init_subclass__
数据模型在类定义中已经这样做了。示例:
class Foo(abc.ABC):
def init(self):
pass
@abc.abstractmethod
def foo():
pass
class Bar(Foo):
pass
此代码将毫无问题地编译。当您通过例如调用子类的构造函数时,错误将首先出现x = Bar()
。如果这是库代码,这意味着错误直到运行时才会出现。另一方面,以下代码:
class Par():
def __init_subclass__(cls, *args, **kwargs):
must_have = 'foo'
if must_have not in list(cls.__dict__.keys()):
raise AttributeError(f"Must have must_have")
def __init__(self):
pass
class Chi(Par):
def __init__(self):
super().__init__()
会抛出错误,因为检查是在类定义时执行的。
通过继承级别强制执行
另一个区别是abstractmethod
希望被修饰的方法被覆盖一次,但__init_subclass__
数据模型也会对子类的子类强制执行约束。示例:
class Foo(abc.ABC):
def __init__(self):
pass
@abc.abstractmethod
def foo():
pass
class Bar(Foo):
def __init__(self):
super().__init__()
def foo(self):
pass
class Mai(Bar):
pass
x = Mai()
此代码将起作用。 Mai
不需要 foo 方法,因为抽象方法已经在 Bar
中被覆盖。另一方面:
class Par():
def __init_subclass__(cls, *args, **kwargs):
must_have = 'foo'
if must_have not in list(cls.__dict__.keys()):
raise AttributeError(f"Must have must_have")
def __init__(self):
pass
class Chi(Par):
def __init__(self):
super().__init__()
def foo(self):
pass
class Chichi(Chi):
def __init__(self):
super().__init__()
这将引发错误,因为Chichi
ALSO 必须有一个 foo 方法,即使两者之间的类有一个。
【讨论】:
以上是关于动态 __init_subclass__ 方法的参数绑定的主要内容,如果未能解决你的问题,请参考以下文章
Python:动态创建类,同时为 __init__subclass__() 提供参数
为啥`type.__new__`调用`__init_subclass__`?
深入理解 Python 中的 __init_subclass__