在python中用父类方法覆盖__init__

Ign*_*cio 1 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()
Run Code Online (Sandbox Code Playgroud)

基本上,我想__init__用父类的工厂类方法覆盖子类的方法。然而,所写的代码不起作用,并引发:

TypeError: __init__() takes 1 positional argument but 3 were given
Run Code Online (Sandbox Code Playgroud)

我认为这意味着作为变量super().with_two_legs('Human')传递。Humancls

1)为什么这不像写的那样工作?我假设super()会返回超类的代理实例,所以clsAnimal对的吗?

2)即使是这种情况,我也不认为这段代码实现了我想要的,因为 classmethod 返回 的实例Animal,但我只想以Human与 classmethod 相同的方式进行初始化,有什么方法可以实现我的行为想?

我希望这不是一个很明显的问题,我发现文件super()有些混乱。

Sha*_*ger 5

super().with_two_legs('Human')的确实际上调用Animalwith_two_legs,但它传递Humancls,没有Animalsuper()使代理对象仅用于辅助方法查找,它不会更改传递的内容(它仍然相同selfcls源自)。在这种情况下,super()甚至没有做任何有用的事情,因为Human没有覆盖with_two_legs,所以:

super().with_two_legs('Human')
Run Code Online (Sandbox Code Playgroud)

表示“with_two_legsHuman定义它的层次结构中的第一个类调用”,并且:

cls.with_two_legs('Human')
Run Code Online (Sandbox Code Playgroud)

意思是“调用with_two_legs层次结构中的第一个类,从cls定义它开始”。只要下面没有类Animal定义它,它们就会做同样的事情。

这意味着您的代码在 处中断return cls(name_full, 2),因为cls仍然是Human,并且您Human.__init__不接受超出 的任何参数self。即使您四处乱搞以使其工作(例如,通过添加两个您忽略的可选参数),这也会导致无限循环,Human.__init__称为Animal.with_two_legs,而后者又会尝试构造Human,Human.__init__再次调用。

你试图做的不是一个好主意;替代构造函数,就其性质而言,取决于类的核心构造函数/初始值设定项。如果您尝试创建依赖于备用构造函数的核心构造函数/初始值设定项,则您已经创建了循环依赖项。

在这种特殊情况下,我建议避免使用备用构造函数,以支持legs始终明确提供计数,或使用TwoLeggedAnimal执行备用构造函数任务的中间类。如果你想重用代码,第二个选项只是意味着你的“从 name 生成 name_full 的超长代码”可以进入TwoLeggedAnimal's __init__; 在第一个选项中,您只需编写一个staticmethod可以分解该代码的因素,以便它可以由with_two_legs需要使用它的其他构造函数和其他构造函数使用。

类层次结构类似于:

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')
Run Code Online (Sandbox Code Playgroud)

通用代码方法将类似于:

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)
Run Code Online (Sandbox Code Playgroud)

旁注:即使您解决了递归,您尝试做的事情也不起作用,因为__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))
Run Code Online (Sandbox Code Playgroud)

无法调用替代构造函数__init__并使其self隐式更改。关于我能想到的在不创建辅助方法并保留备用构造函数的情况下按照您预期的方式进行这项工作的唯一方法是使用__new__代替__init__(因此您可以返回由另一个构造函数创建的实例),并使用显式调用顶级类的替代构造函数以__new__避免循环调用依赖项:

class Animal:
    def __new__(cls, name, legs):  # Use __new__ instead of __init__
        self = super().__new__(cls)  # Constructs base object
        self.legs = legs
        print(name)
        return self  # Returns initialized object
    @classmethod
    def with_two_legs(cls, name):
        # extremely long code to generate name_full from name
        name_full = name
        return Animal.__new__(cls, name_full, 2)  # Explicitly call Animal's __new__ using correct subclass

class Human(Animal):
    def __new__(cls):
        return super().with_two_legs('Human')  # Return result of alternate constructor
Run Code Online (Sandbox Code Playgroud)