继承__init_subclass __-参数

时间:2019-03-15 13:04:55

标签: python-3.x

假设我有一个需要通过__init_subclass__输入一些参数的类:

class AbstractCar:
    def __init__(self):
        self.engine = self.engine_class()

    def __init_subclass__(cls, *, engine_class, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.engine_class = engine_class

class I4Engine:
    pass
class V6Engine:
    pass

class Compact(AbstractCar, engine_class=I4Engine):
    pass
class SUV(AbstractCar, engine_class=V6Engine):
    pass

现在我想从这些派生类之一派生另一个类:

class RedCompact(Compact):
    pass

上面的方法不起作用,因为它希望我重新提供engine_class参数。 现在,我完全理解为什么会发生。这是因为Compact继承自__init_subclass__的{​​{1}},而AbstractCar继承自RedCompact时被调用,随后丢失了预期参数。

我发现这种行为很不直观。毕竟,Compact指定了Compact的所有必需参数,并且应该可以用作完全实现的类。 我完全错误地期望这种行为吗?还有其他机制可以让我实现这种行为吗?

我已经有两个解决方案,但我都找不到。第一个将新的AbstractClass添加到__init_subclass__

Compact

这可行,但是它将class Compact(AbstractCar, engine_class=I4Engine): def __init_subclass__(cls, **kwargs): super().__init_subclass__(engine_class=I4Engine, **kwargs) 类的正确工作责任从该类的编写者转移到用户。另外,它违反了DRY,因为引擎规格现在必须在两个位置保持同步。

我的第二个解决方案在派生类中覆盖了AbstractCar

__init_subclass__

虽然目前可以正常使用,但这是最纯正的黑魔法,一定会在以后造成麻烦。我觉得这不能解决我的问题。

1 个答案:

答案 0 :(得分:0)

的确如此-在Python中这种工作方式是违反直觉的-我同意您的推理。

要解决的方法是在元类中具有一些逻辑。遗憾的是,因为避免使用元类正是创建__init_subclass__的目的。

即使使用元类,也不是一件容易的事-必须在类层次结构中的某个地方注释给__init_subclass__提供的参数,然后在创建新的子类时将其重新插入。

尽管如此,这可以在__init_subclass__内部进行。也就是说:当__init_subclass__“感知”它没有收到应该是强制性的参数时,它将在mro中的类中进行检查(mro“方法解析顺序”-具有所有基类的序列,在订购)。 在这种特定情况下,它可以只检查属性本身-如果mro中至少已经为一个类定义了该属性,则将其保持不变,否则会引发该问题。

如果__init_subclass__中的代码要做的事情比简单地注释传递的参数还要复杂,那么除此之外,该参数应该存储在新类的属性中,以便可以进行相同的检查在下游进行。

简而言之,对于您的代码:

class AbstractCar:
    def __init__(self):
        self.engine = self.engine_class()

    def __init_subclass__(cls, *, engine_class=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if engine_class:
            cls.engine_class = engine_class
            return
        for base in cls.__mro__[1:]:
            if getattr(base, "engine_class", False):
                 return
        raise TypeError("parameter 'engine_class' must be supplied as a class named argument")

我认为这是一个不错的解决方案-可以使用专门用于__init_subclass__的装饰器使其更通用,该装饰器可以将参数存储在命名的类属性中并自动执行此检查-

(我为这样的装饰器编写了代码,但是即使使用inspect模型,也具有命名和未修饰参数的所有极端情况,