派生类__init__中的参数多于基类__init__

big*_*ose 6 python inheritance

如何扩展__init__基类,添加更多要解析的参数,而不需要super().__init__(foo, bar)在每个派生类中?

class Ipsum:
    """ A base ipsum instance. """

    def __init__(self, foo, bar):
        self.foo = flonk(foo)
        grungole(self, bar)
        self._baz = make_the_baz()


class LoremIpsum(Ipsum):
    """ A more refined ipsum that also lorems. """

    def __init__(self, foo, bar, dolor, sit, amet):
        super().__init__(foo, bar)
        farnark(sit, self, amet)
        self.wibble(dolor)
Run Code Online (Sandbox Code Playgroud)

该示例的目的是表明在其中发生了重要的处理Ipsum.__init__,因此不应该在每个子类中重复; 与LoremIpsum.__init__需求foobar参数处理完全相同Ipsum,但也有自己的特殊参数.

它还表明,如果Ipsum需要修改以接受不同的签名,则每个派生类不仅需要更改其签名,还需要更改它如何调用超类__init__.这是不可接受的脆弱.

相反,我想做的事情如下:

class Ipsum:
    """ A base ipsum instance. """

    def __init__(self, foo, bar, **kwargs):
        self.foo = flonk(foo)
        grungole(self, bar)
        self._baz = make_the_baz()

        self.parse_init_kwargs(kwargs)

    def parse_init_kwargs(self, kwargs):
        """ Parse the remaining kwargs to `__init__`. """
        pass


class LoremIpsum(Ipsum):
    """ A more refined ipsum that also lorems. """

    def parse_init_kwargs(self, kwargs):
        (dolor, sit, amet) = (kwargs['dolor'], kwargs['sit'], kwargs['amet'])
        farnark(sit, self, amet)
        self.wibble(dolor)
Run Code Online (Sandbox Code Playgroud)

这有很大的优势是LoremIpsum需要做到这一点是特别的那类零件; 处理Ipsum参数由该类处理,__init__没有任何额外的代码.

但缺点很明显:这通过传递字典有效地重新实现了对命名参数的处理.它避免了很多重复,但不是很清楚.

有哪些工具可以避免子类定义总是需要声明foobar参数,并且总是需要调用super().__init__(foo, bar)?这些很容易出错,所以如果不需要它们会更好,并且可能会自动发生,同时仍允许LoremIpsum自定义初始化.

Kev*_*vin 5

通常的写法大致如下:

class Ipsum:  # 3.x-ism; in 2.x always inherit from object.
    def __init__(self, arg1, arg2, arg3):
        # etc...

class LoremIpsum(Ipsum):
    def __init__(self, arg4, arg5, *args, **kwargs):
        super().__init__(*args, **kwargs)  # 3.x-ism; in 2.x super() needs arguments.
        # Do stuff with arg4 and arg5
Run Code Online (Sandbox Code Playgroud)

这不需要在基类更改签名时修改派生类。您仍然需要修改直接实例化您的基类的所有内容,因此更改签名仍然不是您想要经常做的事情。

这种方法也很优越,因为在多重继承的情况下,它通常或多或少地表现正确,即使您的派生类在方法解析顺序中放在另一个派生类的前面并且您从未听说过其他类(换句话说,即使super().__init__()正在调用一个你一无所知的方法)。这在 Hettinger 的super() 被认为是超级博客文章中有详细讨论。


Ray*_*ger 5

一种灵活的方法是让祖先树中的每个方法协同设计,以接受关键字参数和关键字参数字典,删除所需的任何参数,并使用转发其余参数**kwds,最终将字典留空以进行最终调用在链中.

每个级别剥离它需要的关键字参数,以便最终的空dict可以发送到一个根本不需要参数的方法(例如,object .__ init__期望零参数):

class Shape:
    def __init__(self, shapename, **kwds):
        self.shapename = shapename
        super().__init__(**kwds)        

class ColoredShape(Shape):
    def __init__(self, color, **kwds):
        self.color = color
        super().__init__(**kwds)

cs = ColoredShape(color='red', shapename='circle')
Run Code Online (Sandbox Code Playgroud)

有关此方法的更多信息,请参阅Super Considered Super blogpost的"Practical Advice"部分,或参阅Pycon视频中的相关内容.

在您的示例中,代码如下所示:

class Ipsum:
    """ A base ipsum instance. """

    def __init__(self, foo, bar):
        self.foo = flonk(foo)
        grungole(self, bar)
        self._baz = make_the_baz()

class LoremIpsum(Ipsum):
    """ A more refined ipsum that also lorems. """

    def __init__(self, dolor, sit, amet, **kwds):
        super().__init__(**kwds)
        farnark(sit, self, amet)
        self.wibble(dolor)
Run Code Online (Sandbox Code Playgroud)

实例化将如下所示:

li = LoremIpsum(dolor=1, sit=2, amet=3, foo=4, bar=5) 
Run Code Online (Sandbox Code Playgroud)

注意,这使您可以实现向__init__方法添加新参数而不影响另一个的原始目标.

  • 我看到的一个缺点是:有人在检查 `LoremIpsum.__init__` 方法的签名时没有看到那里提到的重要的 `foo` 和 `bar` 参数。然而,在每个派生类中提及它们也是有问题的。这就是为什么我希望有一种方法,这意味着我不需要在每个派生类中重新实现 `__init__`。 (3认同)