Py学习  »  Python

在python中用父类方法重写init

Ignacio • 4 年前 • 1122 次点击  

我想做如下事情(在Python3.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() 将返回超类的代理实例,因此 cls公司 会是 Animal 正确的?

2) 即使是这样,我也不认为这段代码实现了我想要的,因为classmethod返回了 动物 ,但我只想初始化 和classmethod一样,有什么方法可以实现我想要的行为吗?

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

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/52310
 
1122 次点击  
文章 [ 2 ]  |  最新文章 4 年前
Blckknght
Reply   •   1 楼
Blckknght    4 年前

从调用中获取的代理对象 super 只是用来定位 with_two_legs 要调用的方法(因为您没有在 Human ,你可以用 self.with_two_legs 同样的结果)。

作为 wim 评论道,你的替代构造函数 有两条腿 不起作用,因为 班级休息 Liskov substitution principle 通过使用不同的构造函数签名。即使你能让代码调用 Animal 为了构建你的实例,你会遇到问题,因为你最终会得到一个 动物 实例而不是 一个(所以其他方法 ,如果您写了一些,将无法使用)。

注意,这种情况并不少见,许多Python子类的构造函数签名与其父类不同。但这确实意味着不能像使用 classmethod 试图构造实例的。你需要避免这些情况。

在这种情况下,最好使用 legs 对…的争论 动物 构造器。它可以默认为 2 如果没有通过替代编号,则为支腿。那你就不需要 类方法 ,并且在重写时不会遇到问题 __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()
ShadowRanger
Reply   •   2 楼
ShadowRanger    4 年前

super().with_two_legs('Human') 事实上 Animal with_two_legs ,但它通过了 Human 作为 cls ,不是 动物 . super() 使代理对象仅用于帮助方法查找,它不会更改传递的内容(它仍然是相同的 self cls公司 它起源于)。在这种情况下, 超级() 什么都没用,因为 不覆盖 有两条腿 ,所以:

super().with_two_legs('Human')

意思是“呼叫 有两条腿 一等以上 在定义它的层次结构中”,以及:

cls.with_two_legs('Human')

意思是“呼叫 有两条腿 在层次结构中的第一个类上 cls公司 这就是它的定义”。只要下面没有课 动物 定义它,它们做同样的事情。

这意味着你的代码在 return cls(name_full, 2) ,因为 cls公司 仍然 ,以及您的 Human.__init__ 不带任何争论 自己 . 即使你想让它起作用(例如添加两个你忽略的可选参数),这也会导致无限循环,如 人类__ 打电话 Animal.with_two_legs ,它反过来试图构造 ,呼叫 人类__ 再一次。

您要做的并不是一个好主意;根据其性质,备用构造函数依赖于类的核心构造函数/初始值设定项。如果尝试生成依赖于备用构造函数的核心构造函数/初始值设定项,则创建了循环依赖项。

在这种情况下,我建议避免使用备用构造函数,而是显式地提供 legs 始终计数,或使用中间值 TwoLeggedAnimal 类执行备用构造函数的任务。如果你想重用代码,第二个选项就意味着你的“从名称生成完整名称的非常长的代码”可以进入 双腿动物 __init__ ;在第一个选项中,您只需编写 staticmethod 这就排除了代码的因素,所以这两种代码都可以使用 有两条腿 以及其他需要使用它的构造函数。

类层次结构类似于:

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)

旁注:即使你处理递归,你所要做的也不会起作用,因为 __初始__ 制作 新实例,它初始化现有实例。所以即使你打电话 super()。有两条腿(“人类”) 它以某种方式工作,它生成并返回一个完全不同的实例,但对 自己 接收人 __初始__ 这就是真正被创造出来的东西。你能做的最好的事情是:

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))

无法在中调用备用构造函数 __初始__ 换个衣服 自己 含蓄地。我能想到的唯一办法是,在不创建helper方法和保留备用构造函数的情况下,使用 __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