社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

在python中用父类方法重写init

Ignacio • 5 年前 • 1855 次点击  

我想做如下事情(在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
 
1855 次点击  
文章 [ 2 ]  |  最新文章 5 年前
Blckknght
Reply   •   1 楼
Blckknght    5 年前

从调用中获取的代理对象 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    5 年前

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