隐式调用父类初始值设定项

时间:2010-03-01 09:17:58

标签: python constructor super python-2.x method-resolution-order

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,而不是{{ 1}}就像我一直假设的那样。

在Python-2.x中有什么方法可以在调用超级构造函数时保持正确的MRO(关于以正确的顺序初始化所有父类),而不是命名当前类({{1在} B)中?

4 个答案:

答案 0 :(得分:3)

简短回答:不,没有办法在Python 2.x中使用右父类的正确参数隐式调用右__init__

顺便提一下,此处显示的代码不正确:如果使用super()。__init__,则层次结构中的所有类必须在其__init__方法中具有相同的签名。否则,如果引入使用多重继承的新子类,则代码可能会停止工作。

有关问题的更长描述(包括图片),请参阅http://fuhm.net/super-harmful/

答案 1 :(得分:1)

也许您要找的是元类?

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

打印“我是A级”

元类的作用是覆盖B类(不是对象)的初始创建,并确保每个B对象的内置字典现在包含一个基础数组,您可以在其中找到B的所有基类

答案 2 :(得分:1)

您的代码与方法解析顺序无关。方法解决方案出现在多重继承的情况下,而不是您的示例。您的代码完全错误,因为您认为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

template pattern的修改版本)。

现在父母的方法实际上是隐式调用的,但我必须同意python zen,其中显式优于隐式,因为代码可读性较差且增益较差。但要注意所有_init方法都有相同的参数,你不能完全忘记父母,我不建议这样做。

对于单继承,更好的方法是显式调用parent的方法,而不调用super。这样做你不必命名当前的类,但你仍然必须关心谁是父类。

好的读物是:how-does-pythons-super-do-the-right-thing以及该问题中提出的链接,特别是Python's Super is nifty, but you can't use it

如果层次结构可能发生变化,则表明设计不良,并且在使用该代码的所有部分都会产生影响,不应该受到鼓励。

编辑2

另一个例子是我的想法,但它使用元类。 Urwid库uses 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
>>> 

答案 3 :(得分:0)

据我所知,以下情况并不常见。但它似乎确实有效。

给定类定义中的方法总是会破坏双下划线属性以包含它们所定义的类的名称。因此,如果您以名称错位的形式存储对该类的引用,实例可以在其中查看它,您可以在super的调用中使用它。

通过在基类上实现__new__来隐藏对象本身的引用的示例:

def mangle(cls, name):
    if not name.startswith('__'):
        raise ValueError('name must start with double underscore')
    return '_%s%s' % (cls.__name__, name)

class ClassStasher(object):
    def __new__(cls, *args, **kwargs):
        obj = object.__new__(cls)
        for c in cls.mro():
            setattr(obj, mangle(c, '__class'), c)
        return obj

class A(ClassStasher):
    def __init__(self):
        print 'init in A', self.__class
        super(self.__class, self).__init__()

class B(A):
    def __init__(self):
        print 'init in B', self.__class
        super(self.__class, self).__init__()

class C(A):
    def __init__(self):
        print 'init in C', self.__class
        super(self.__class, self).__init__()

class D(B, C):
    def __init__(self):
        print 'init in D', self.__class
        super(self.__class, self).__init__()


d = D()    
print d

并且做了类似的事情,但是使用了一个元类并且隐藏了类对象本身的__class引用:

class ClassStasherType(type):
    def __init__(cls, name, bases, attributes):
        setattr(cls, mangle(cls, '__class'), cls)

class ClassStasher(object):
    __metaclass__ = ClassStasherType

class A_meta(ClassStasher):
    def __init__(self):
        print 'init in A_meta', self.__class
        super(self.__class, self).__init__()

class B_meta(A_meta):
    def __init__(self):
        print 'init in B_meta', self.__class
        super(self.__class, self).__init__()

class C_meta(A_meta):
    def __init__(self):
        print 'init in C_meta', self.__class
        super(self.__class, self).__init__()

class D_meta(B_meta, C_meta):
    def __init__(self):
        print 'init in D_meta', self.__class
        super(self.__class, self).__init__()


d = D_meta()    
print d

将所有这些作为一个源文件一起运行:

% python /tmp/junk.py
init in D <class '__main__.D'>
init in B <class '__main__.B'>
init in C <class '__main__.C'>
init in A <class '__main__.A'>
<__main__.D object at 0x1004a4a50>
init in D_meta <class '__main__.D_meta'>
init in B_meta <class '__main__.B_meta'>
init in C_meta <class '__main__.C_meta'>
init in A_meta <class '__main__.A_meta'>
<__main__.D_meta object at 0x1004a4bd0>