隐式调用父类初始化器
class A(object):
def __init__(self, a, b, c):
#super(A, self).__init__()
super(self.__class__, self).__init__()
class B(A):
def __init__(self, b, c):
print super(B, self)
print super(self.__class__, self)
#super(B, self).__init__(1, b, c)
super(self.__class__, self).__init__(1, b, c)
class C(B):
def __init__(self, c):
#super(C, self).__init__(2, c)
super(self.__class__, self).__init__(2, c)
C(3)
在上面的代码中,被注释掉的 __init__
调用看起来是大家普遍接受的“聪明”方式来进行父类的初始化。不过,如果类的层级结构可能会改变,我最近一直在使用没有注释的那种写法。
在上面的层级结构中,调用 B
的父类构造函数时,发现 B.__init__
又被调用了一次,而 self.__class__
实际上是 C
,而不是我一直认为的 B
。
有没有什么方法可以在 Python 2.x 中,在调用父类构造函数时,保持正确的 MRO(即以正确的顺序初始化所有父类),而不需要直接指定当前类(在 super(B, self).__init__(1, b, c)
中的 B
)?
4 个回答
你的代码和方法解析顺序没有关系。方法解析顺序主要是在多重继承的情况下才会涉及,而你的例子并不是这种情况。你的代码其实是错误的,因为你假设 self.__class__
和定义方法的类是同一个类,这个假设是错的:
>>> class A(object):
... def __init__(self):
... print self.__class__
...
>>>
>>> class B(A):
... def __init__(self):
... A.__init__(self)
...
>>> B()
<class '__main__.B'>
<__main__.B object at 0x1bcfed0>
>>> A()
<class '__main__.A'>
<__main__.A object at 0x1bcff90>
>>>
所以当你应该调用:
super(B, self).__init__(1, b, c)
你实际上调用的是:
# super(self.__class__, self).__init__(1, b, c)
super(C, self).__init__(1, b, c)
编辑:试着更好地回答这个问题。
class A(object):
def __init__(self, a):
for cls in self.__class__.mro():
if cls is not object:
cls._init(self, a)
def _init(self, a):
print 'A._init'
self.a = a
class B(A):
def _init(self, a):
print 'B._init'
class C(A):
def _init(self, a):
print 'C._init'
class D(B, C):
def _init(self, a):
print 'D._init'
d = D(3)
print d.a
打印结果是:
D._init
B._init
C._init
A._init
3
(这是一个修改过的 模板方法模式 的版本。)
现在父类的方法确实是隐式调用的,但我得同意 Python 的哲学,明确调用比隐式调用要好,因为这样代码更易读,而且收益也不大。不过要注意,所有的 __init__
方法都有相同的参数,你不能完全忽视父类,我也不建议这样做。
对于单一继承,比较好的做法是明确调用父类的方法,而不是使用 super
。这样你就不需要 指定当前类的名字,但仍然需要关注父类是谁。
推荐阅读的内容有: Python 的 super 是如何正确工作的 以及在那个问题中提到的链接,特别是 Python 的 Super 很巧妙,但你不能使用它
如果继承层次结构可能会改变,那就是设计不好的表现,会对使用这段代码的所有部分产生影响,这种情况不应该被鼓励。
编辑 2
我想到另一个例子,但它使用了 metaclass。Urwid 库 使用 metaclass 来存储一个属性 __super
,这样你只需要访问这个属性。
例如:
>>> class MetaSuper(type):
... """adding .__super"""
... def __init__(cls, name, bases, d):
... super(MetaSuper, cls).__init__(name, bases, d)
... if hasattr(cls, "_%s__super" % name):
... raise AttributeError, "Class has same name as one of its super classes"
... setattr(cls, "_%s__super" % name, super(cls))
...
>>> class A:
... __metaclass__ = MetaSuper
... def __init__(self, a):
... self.a = a
... print 'A.__init__'
...
>>> class B(A):
... def __init__(self, a):
... print 'B.__init__'
... self.__super.__init__(a)
...
>>> b = B(42)
B.__init__
A.__init__
>>> b.a
42
>>>
也许你在寻找的是元类?
class metawrap(type):
def __new__(mcs,name, bases, dict):
dict['bases'] = bases
return type.__new__(mcs,name,bases,dict)
class A(object):
def __init__(self):
pass
def test(self):
print "I am class A"
class B(A):
__metaclass__ = metawrap
def __init__(self):
pass
def test(self):
par = super(self.bases[0],self)
par.__thisclass__.test(self)
foo = B()
foo.test()
这段代码会打印出“I am class A”
元类的作用是重新定义B类的创建过程(不是对象的创建),确保每个B对象的内置字典里都有一个基类数组,这样你就可以找到B类的所有父类了。
简单来说:不行,在Python 2.x中,没有办法自动调用正确的__init__
方法,并且传入正确的参数和父类。
顺便提一下,这里展示的代码是错误的:如果你使用super().__init__
,那么你所有的类都必须在它们的__init__
方法中有相同的参数。如果不这样做,当你引入一个使用多重继承的新子类时,你的代码可能会停止工作。
想了解更多这个问题的详细信息(还有图片),可以查看http://fuhm.net/super-harmful/。