简体   繁体   English

用python中的父类方法重写__init__

[英]Overriding __init__ with parent classmethod in python

I want to do something like the following (in Python 3.7): 我想执行以下操作(在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()

Basically, I want to override the __init__ method of a child class with a factory classmethod of the parent. 基本上,我想用父级的工厂类方法重写子级的__init__方法。 The code as written, however, does not work, and raises: 但是,编写的代码不起作用,并引发:

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

I think this means that super().with_two_legs('Human') passes Human as the cls variable. 我认为这意味着super().with_two_legs('Human')Human作为cls变量传递。

1) Why doesn't this work as written? 1)为什么这不能按书面要求进行? I assumed super() would return a proxy instance of the superclass, so cls would be Animal right? 我假设super()将返回超类的代理实例,所以clsAnimal吗?

2) Even if this was the case I don't think this code achieves what I want, since the classmethod returns an instance of Animal , but I just want to initialize Human in the same way classmethod does, is there any way to achieve the behaviour I want? 2)即使是这种情况,我也不认为这段代码可以达到我想要的效果,因为classmethod返回了Animal的实例,但是我只想以与classmethod相同的方式初始化Human ,是否有任何方法可以实现我想要的行为?

I hope this is not a very obvious question, I found the documentation on super() somewhat confusing. 我希望这不是一个很明显的问题,我发现关于super()文档有些混乱。

super().with_two_legs('Human') does in fact call Animal 's with_two_legs , but it passes Human as the cls , not Animal . super().with_two_legs('Human')实际上确实调用Animalwith_two_legs ,但它将Human作为cls而不是Animal传递。 super() makes the proxy object only to assist with method lookup, it doesn't change what gets passed (it's still the same self or cls it originated from). super()使代理对象仅用于辅助方法查找,它不会更改传递的内容(仍然是相同的selfcls源自的cls )。 In this case, super() isn't even doing anything useful, because Human doesn't override with_two_legs , so: 在这种情况下, super()甚至没有做任何有用的事情,因为Human不会覆盖with_two_legs ,因此:

super().with_two_legs('Human')

means "call with_two_legs from the first class above Human in the hierarchy which defines it", and: 表示“在定义它的层次结构中,从“ Humanwith_two_legs的第一类调用with_two_legs ”,并且:

cls.with_two_legs('Human')

means "call with_two_legs on the first class in the hierarchy starting with cls that defines it". 意思是“从定义它的cls开始,在层次结构中的第一个类上调用with_two_legs ”。 As long as no class below Animal defines it, those do the same thing. 只要Animal下面没有类定义它,它们就可以做同样的事情。

This means your code breaks at return cls(name_full, 2) , because cls is still Human , and your Human.__init__ doesn't take any arguments beyond self . 这意味着您的代码在return cls(name_full, 2)处中断,因为cls仍然是Human ,并且Human.__init__不会接受self以外的任何参数。 Even if you futzed around to make it work (eg by adding two optional arguments that you ignore), this would cause an infinite loop, as Human.__init__ called Animal.with_two_legs , which in turn tried to construct a Human , calling Human.__init__ again. 即使您四处寻找使其工作(例如,通过添加两个忽略的可选参数),也将导致无限循环,因为Human.__init__称为Animal.with_two_legs ,而后者又试图构造一个Human ,称为Human.__init__再次。

What you're trying to do is not a great idea; 您尝试做的并不是一个好主意; alternate constructors, by their nature, depend on the core constructor/initializer for the class. 备用构造函数,就其性质而言,取决于类的核心构造函数/初始化程序。 If you try to make a core constructor/initializer that relies on an alternate constructor, you've created a circular dependency. 如果尝试创建依赖于替代构造函数的核心构造函数/初始化程序,则已创建了循环依赖项。

In this particular case, I'd recommend avoiding the alternate constructor, in favor of either explicitly providing the legs count always, or using an intermediate TwoLeggedAnimal class that performs the task of your alternate constructor. 在这种特殊情况下,我建议避免使用替代构造函数,而建议显式提供始终提供的legs数,或者使用中间的TwoLeggedAnimal类来执行替代构造函数的任务。 If you want to reuse code, the second option just means your "extremely long code to generate name_full from name" can go in TwoLeggedAnimal 's __init__ ; 如果您想重用代码,第二个选项仅表示您的“从名称生成name_full的超长代码”可以放在TwoLeggedAnimal__init__ in the first option, you'd just write a staticmethod that factors out that code so it can be used by both with_two_legs and other constructors that need to use it. 在第一个选项中,您只需要编写一个staticmethod即可将代码排除在外,因此with_two_legs和其他需要使用它的构造函数都可以使用它。

The class hierarchy would look something like: 类层次结构如下所示:

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

The common code approach would instead be something like: 通用代码方法将改为:

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)

Side-note: What you were trying to do wouldn't work even if you worked around the recursion, because __init__ doesn't make new instances, it initializes existing instances. 旁注:即使您解决了递归问题,您尝试执行的操作也不起作用,因为__init__不会创建新实例,它会初始化现有实例。 So even if you call super().with_two_legs('Human') and it somehow works, it's making and returning a completely different instance, but not doing anything to the self received by __init__ which is what's actually being created. 因此,即使您调用super().with_two_legs('Human')并且以某种方式起作用,它super().with_two_legs('Human')并返回一个完全不同的实例,但对__init__接收到的self不做任何事情,这实际上是在创建的东西。 The best you'd have been able to do is something like: 您可能要做的最好的事情是:

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

There is no way to call an alternate constructor in __init__ and have it change self implicitly. 无法在__init__调用备用构造函数并使它隐式更改self About the only way I can think of to make this work in the way you intended without creating helper methods and preserving your alternate constructor would be to use __new__ instead of __init__ (so you can return an instance created by another constructor), and doing awful things with the alternate constructor to explicitly call the top class's __new__ to avoid circular calling dependencies: 我可以想到的按此方法按预期方式进行工作而不创建辅助方法并保留备用构造函数的唯一方法是使用__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

The proxy object you get from calling super was only used to locate the with_two_legs method to be called (and since you didn't override it in Human , you could have used self.with_two_legs for the same result). 从调用super获得的代理对象仅用于定位要调用的with_two_legs方法(由于您没有在Human覆盖它,因此您可以使用self.with_two_legs获得相同的结果)。

As wim commented, your alternative constructor with_two_legs doesn't work because the Human class breaks the Liskov substitution principle by having a different constructor signature. 正如wim所评论的那样,您的替代构造函数with_two_legs不起作用,因为Human类通过具有不同的构造函数签名而破坏了Liskov替换原理 Even if you could get the code to call Animal to build your instance, you'd have problems because you'd end up with an Animal instances and not a Human one (so other methods in Human , if you wrote some, would not be available). 即使您可以获取调用Animal的代码来构建实例,您也会遇到问题,因为最终将获得Animal实例而不是Human实例(因此Human其他方法(如果您编写了一些实例)可用)。

Note that this situation is not that uncommon, many Python subclasses have different constructor signatures than their parent classes. 请注意,这种情况并不少见,许多Python子类的构造函数签名与其父类不同。 But it does mean that you can't use one class freely in place of the other, as happens with a classmethod that tries to construct instances. 但它确实意味着你不能代替其他的自由使用一个类,如用发生classmethod ,试图构建实例。 You need to avoid those situations. 您需要避免这些情况。

In this case, you are probably best served by using a default value for the legs argument to the Animal constructor. 在这种情况下,最好为Animal构造函数的legs参数使用默认值,从而为您提供最佳服务。 It can default to 2 legs if no alternative number is passed. 如果未传递替代编号,则默认为2 Then you don't need the classmethod , and you don't run into problems when you override __init__ : 这样就不需要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()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM