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

时间:2016-10-06 04:16:22

标签: 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)

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

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

相反,我想做类似的事情:

__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) 只需要 做那个特殊的部分;处理LoremIpsum参数由该类Ipsum处理,无需任何额外代码。

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

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

3 个答案:

答案 0 :(得分: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')

有关此方法的更多信息,请参阅"实用建议" Super Considered Super博文的部分,或查看Pycon video上的相关内容。

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

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)

实例化将如下所示:

li = LoremIpsum(dolor=1, sit=2, amet=3, foo=4, bar=5) 

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

答案 1 :(得分:3)

写这些的通常方法大致如下:

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

当基类更改签名时,不需要修改派生类。您仍然需要修改直接实例化基类的所有内容,因此更改签名仍然不是您想要经常执行的操作。

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

答案 2 :(得分:-1)

派生类需要调用超类__init__。这就是面向对象编程的工作原理。它脆吗?是的,它可以;这就是为什么深层次或广泛的阶级层次结构通常不是一个好主意的原因。如果foo中的barLorem上的操作很重要(正如您所说的那样),那么正确调用Lorem.__init__()是必须支付的费用。 foobar(以及任何新参数)的默认值是我能想到的唯一可以改变所需文本更改量的工具,但默认值并不总是合适的,因此缓解可能不适用。