python:在继承的类中连续调用?
我遇到了一个我不理解的问题,它非常复杂,所以我尽我所能在这里把它分解。请看一下下面的实现。我的问题是:为什么父类调用其子类的_getitem_uuuuu方法,而不是调用自己的_getitem_uuu方法python:在继承的类中连续调用?,python,inheritance,call,python-2.x,Python,Inheritance,Call,Python 2.x,我遇到了一个我不理解的问题,它非常复杂,所以我尽我所能在这里把它分解。请看一下下面的实现。我的问题是:为什么父类调用其子类的_getitem_uuuuu方法,而不是调用自己的_getitem_uuu方法 class Father(object): ''' Father class ''' def __init__(self,name,gender): print('call __init__ Father') self._name =
class Father(object):
''' Father class '''
def __init__(self,name,gender):
print('call __init__ Father')
self._name = name
# trying to call Father's __getitem__ here
self._gender=self[gender]
def __getitem__(self,key):
print('call __getitem__ Father')
return key
class Child(Father):
''' inherited from Father class '''
def __init__(self, name, gender, age=None):
print('call __init__ Child')
super(self.__class__, self).__init__(name, gender)
self._age = age
def __getitem__(self, key):
print('call __getitem__ Child')
if self._name == 'Marie' and self._age == None:
print('I am not sure, how old I am')
return super(self.__class__, self).__getitem__(key)
one=Child('Klaus','male','12')
other=Child('Marie','female')
将出现的错误消息是:
call __init__ Child
call __init__ Father
call __getitem__ Child
call __getitem__ Father
call __init__ Child
call __init__ Father
call __getitem__ Child
Traceback (most recent call last):
File "<ipython-input-58-2d97b09f6e25>", line 1, in <module>
runfile('F:/python-workspace/more-fancy-getitem-fix/test.py', wdir='F:/python-workspace/more-fancy-getitem-fix')
File "C:\Python27\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.py", line 601, in runfile
execfile(filename, namespace)
File "C:\Python27\lib\site-packages\spyderlib\widgets\externalshell\sitecustomize.py", line 66, in execfile
exec(compile(scripttext, filename, 'exec'), glob, loc)
File "F:/python-workspace/more-fancy-getitem-fix/test.py", line 26, in <module>
other=Child('Marie','female')
File "F:/python-workspace/more-fancy-getitem-fix/test.py", line 16, in __init__
super(self.__class__, self).__init__(name, gender)
File "F:/python-workspace/more-fancy-getitem-fix/test.py", line 6, in __init__
self._gender=self[gender]
File "F:/python-workspace/more-fancy-getitem-fix/test.py", line 21, in __getitem__
if self._name == 'Marie' and self._age == None:
AttributeError: 'Child' object has no attribute '_age'
不幸的是,没有做到这一点,并产生了相同的错误。要修复此问题,只需交换
子项\uuuuu init\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu>中的初始化顺序,因此子项所需的属性在需要之前被初始化:
def __init__(self, name, gender, age=None):
print('call __init__ Child')
self._age = age
super(Child, self).__init__(name, gender)
此修复对于此特定场景是唯一的,但通常也是正确的修复;一般来说,父类的唯一责任是尽量不要不必要地破坏子类化,但不能期望它们计划子类创建新的类不变量,并尝试提前适应它们。子类应该完全了解父类,并且子类有责任确保它不会破坏父类的任何行为,在这种情况下,确保在父类使用需要它们的方法之前定义所有新的必需属性
至于它为什么调用子对象的\uu getitem\uu
,这就是默认情况下子类化的工作方式(不仅在Python中,而且在大多数OO语言中)。父亲从子对象收到的自我
。\uuuuuu init\uuuuuuu
仍然是子对象
对象,因此查找首先检查子对象上的\uuuuu getitem\uuuuuu
。如果不这样做,您将有混乱的行为,其中子项
上的一些索引调用了\uu getitem\uuuu
,而另一些没有调用,这将使它变得非常不直观
如果出于某种原因,您必须在Father.\uuuuu init\uuuuu
中只使用Father
的\uuuu getitem\uuuuuuuuuuuu
,那么只需明确说明您调用的方法。不要使用self[gender]
或self.\uu getitem\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu(而且还要求您显式地传递self
,因为您使用的是unbound方法而不是bound方法)
另一种方法是定义一个指示实例未完全初始化的属性,并在父初始值设定项期间绕过子重载,以便在初始化期间调用父方法:
def __init__(self, name, gender, age=None):
print('call __init__ Child')
# Use double underscore prefix to make a private attribute of Child
# not directly visible to parent or lower level children so no risk
# of being reused by accident
self.__in_parent_init = True
super(Child, self).__init__(name, gender)
self.__in_parent_init = False
self._age = age
def __getitem__(self, key):
if self.__in_parent_init:
# Don't use Child specific features until Father initialized
return super(Child, self).__getitem__(key)
print('call __getitem__ Child')
if self._name == 'Marie' and self._age == None:
print('I am not sure, how old I am')
return super(Child, self).__getitem__(key)
这里使用的\uuu getitem\uuuu
方法将通过实际的self
类进行查找。如果self
是Child
的一个实例,它将使用Child.\uu getitem\uuuuu
。您可以将其更改为使用父类。\uu getitem\uuu
明确:
self._gender = Father.__getitem__(self, gender)
虽然您的总体设计仍然有点混乱,而且可能会导致更多的问题
不相关但重要的:
super(self.__class__, self)
永远不要这样做。如果您在任何子类化的类中这样做,这将中断。您必须显式提供类名。您不应该使用super(self.\uu class\uuuuuuuuuuuuuu,self)
永远。如果我创建一个类孙子(Child),这将永远递归:pass
澄清@Eric的观点:在Python3中,您只需使用简单而正确的super()
。在Python2中,您需要调用通过名称显式定义方法的类,例如在您的代码中,super(Child,self)
,因此在继承情况下,super
魔术可以跟踪哪些重载已经被调用。self.\uuuu class\uuuu
将根据您在继承层次结构中的哪个级别进行更改,并且只对第一个super
调用正确;之后,它将传递子类,不是吗t父类应该是这样的。感谢这个提示,我认为这个语法更通用,如果我想更改父类的名称,我不必在这里重命名。我没有想到相反的方式。“只需在子类中交换初始化顺序即可。”-这在这里是可行的,但试图通过在更复杂的系统中重新排序初始化来解决问题可能会导致一个可怕的微妙依赖关系网络。构造函数最好不要依赖子类方法或子类初始化。@user2357112:同意这是特定于这种情况的。也就是说,继承从来没有像我们希望的那样干净t to be;如果父亲来自外部代码库,并且没有显式地使用父亲(self,gender)
(因为它没有预料到孩子会以破坏性的方式重写父亲),这不是父亲方面可以解决的问题,将处理父类怪癖的责任留给子类通常是合理的(子类完全了解父类,而父类通常不了解子类)。谢谢你这么好的回答!切换确实是一个解决方案,但是把事情搞混了,以后可能会让我头疼。父亲的明确呼叫是正确的提示,谢谢。@ShadowRanger:如果父亲是这样写的,它真的不应该被子类化。子类不应该完全了解e父类的实现详细信息,特别是在您描述父类是第三方代码的情况下。谁知道第三方库将如何更改?如果一个类的公共方法相互依赖,并且不是专门设计用于支持子类化的,那么重写这些方法是不安全的Puthon并不真正相信严格的知识分离,比如java和C++语言。
self._gender = Father.__getitem__(self, gender)
super(self.__class__, self)