用python中的父类方法重写__init__

时间:2019-08-05 22:01:03

标签: python inheritance constructor class-method

我想做以下事情(在Python 3.7中):

class Animal:

    def __init__(self, name, legs):
        self.legs = legs
        print(name)

    @classmethod 
    def with_two_legs(cls, name):
        # extremely long code to generate name_full from name
        name_full = name
        return cls(name_full, 2)

class Human(Animal):

    def __init__(self):
        super().with_two_legs('Human')

john = Human()

基本上,我想用父级的工厂类方法重写子级的__init__方法。但是,所编写的代码不起作用,并引发:

TypeError: __init__() takes 1 positional argument but 3 were given

我认为这意味着super().with_two_legs('Human')通过Human作为cls变量。

1)为什么这不能按书面要求进行?我假设super()将返回超类的代理实例,所以clsAnimal对吗?

2)即使是这种情况,我也认为该代码无法实现我想要的结果,因为类方法返回了Animal的实例,但是我只想在同一实例中初始化Human classmethod的方法,有什么方法可以实现我想要的行为?

我希望这不是一个很明显的问题,我发现super()上的documentation有点令人困惑。

2 个答案:

答案 0 :(得分:2)

super().with_two_legs('Human')实际上确实调用了Animal的{​​{1}},但是它将with_two_legs作为Human而不是cls传递。 Animal使代理对象仅用于协助方法查找,它不会更改传递的内容(仍与源于的super()self相同)。在这种情况下,cls甚至没有做任何有用的事情,因为super()不会覆盖Human,所以:

with_two_legs

表示“在定义它的层次结构中,从super().with_two_legs('Human') 以上的第一类调用with_two_legs”,

Human

表示“从定义它的cls.with_two_legs('Human') 开始,在层次结构中的第一个类上调用with_two_legs”。只要cls下面没有类定义它,它们就会做同样的事情。

这意味着您的代码在Animal处中断,因为return cls(name_full, 2)仍然是cls,并且您的Human不接受Human.__init__以外的任何参数。即使您四处寻找使其工作(例如,通过添加两个忽略的可选参数),也将导致无限循环,如self称为Human.__init__,这反过来又试图构造一个{{ 1}},再次调用Animal.with_two_legs

您要尝试做的并不是一个好主意;备用构造函数,就其性质而言,取决于类的核心构造函数/初始化程序。如果您尝试创建一个依赖于替代构造函数的核心构造函数/初始化程序,那么您已经创建了循环依赖项。

在这种情况下,我建议避免使用替代构造函数,而建议始终显式提供Human计数,或者使用中间的Human.__init__类来执行替代构造函数的任务。如果要重用代码,第二个选项仅表示您的“从名称生成name_full的超长代码”可以放入legs的{​​{1}}中;在第一个选项中,您只需编写一个TwoLeggedAnimal即可将代码排除在外,以便TwoLeggedAnimal和其他需要使用它的构造函数都可以使用它。

类层次结构看起来像:

__init__

常见的代码方法将类似于:

staticmethod

旁注:即使您绕过递归,您尝试执行的操作也不起作用,因为with_two_legs不会 make 新实例,它将初始化现有实例。因此,即使您调用class Animal: def __init__(self, name, legs): self.legs = legs print(name) class TwoLeggedAnimal(Animal) def __init__(self, name): # extremely long code to generate name_full from name name_full = name super().__init__(name_full, 2) class Human(TwoLeggedAnimal): def __init__(self): super().__init__('Human') 并以某种方式起作用,它也会生成并返回一个完全不同的实例,但对class Animal: def __init__(self, name, legs): self.legs = legs print(name) @staticmethod def _make_two_legged_name(basename): # extremely long code to generate name_full from name return name_full @classmethod def with_two_legs(cls, name): return cls(cls._make_two_legged_name(name), 2) class Human(Animal): def __init__(self): super().__init__(self._make_two_legged_name('Human'), 2) 接收到的__init__却没有做任何事情,这实际上是在创建。您能做的最好的事情是:

super().with_two_legs('Human')

无法在self中调用备用构造函数并使它隐式更改__init__。我可以想到的按此方法按预期方式进行工作而不创建辅助方法并保留备用构造函数的唯一方法是使用def __init__(self): self_template = super().with_two_legs('Human') # Cheaty way to copy all attributes from self_template to self, assuming no use # of __slots__ vars(self).update(vars(self_template)) 而不是__init__(因此您可以返回创建的实例(由另一个构造函数),并使用备用构造函数做糟糕的事情,以显式调用顶级类的self以避免循环调用依赖项:

__new__

答案 1 :(得分:1)

从调用super获得的代理对象仅用于定位要调用的with_two_legs方法(并且由于您没有在Human中覆盖它,因此可以使用self.with_two_legs获得相同的结果。

正如wim所评论的那样,您的备用构造函数with_two_legs不起作用,因为Human类通过具有不同的构造函数签名来破坏Liskov substitution principle。即使您可以获取调用Animal的代码来构建实例,也可能会遇到问题,因为最终将得到一个Animal实例而不是一个Human实例(其他Human中的方法(如果您写了一些方法,将不可用)。

请注意,这种情况并不少见,许多Python子类的构造函数签名与其父类不同。但这确实意味着您不能随意使用一个类来代替另一个类,就像试图构造实例的classmethod那样。您需要避免这些情况。

在这种情况下,最好为legs构造函数的Animal参数使用默认值来为您提供最佳服务。如果未传递任何替代数字,则可以默认为2个分支。这样就不需要classmethod,并且在覆盖__init__时也不会遇到问题:

class Animal:
    def __init__(self, name, legs=2): # legs is now optional, defaults to 2
        self.legs = legs
        print(name)

class Human(Animal):
    def __init__(self):
        super().__init__('Human')

john = Human()