[英]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()
将返回超类的代理实例,所以cls
是Animal
吗?
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')
实际上确实调用Animal
的with_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()
使代理对象仅用于辅助方法查找,它不会更改传递的内容(仍然是相同的self
或cls
源自的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: 表示“在定义它的层次结构中,从“
Human
” with_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.