7
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 中是否有某种方法可以在调用超级构造函数而不命名当前类(Bin in )时维护正确的 MRO(关于以正确的顺序初始化所有父类super(B, self).__init__(1, b, c))?

4

4 回答 4

3

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

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

请参阅http://fuhm.net/super-harmful/以获得对该问题的详细描述(附图片)。

于 2010-05-03T15:26:05.707 回答
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

模板模式的修改版本)。

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

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

好的读物是:how-does-pythons-super-do-the-right-thing以及该问题中建议的链接,特别是Python 的 Super 很漂亮,但你不能使用它

如果层次结构可能发生变化是糟糕设计的症状,并且会对使用该代码的所有部分产生影响,因此不应鼓励。

编辑 2

我想到了另一个例子,但它使用了元类。Urwid 库使用元类在类中存储属性 ,__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
>>> 
于 2010-04-30T08:24:05.337 回答
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 的所有基类

于 2010-03-01T10:32:09.350 回答
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>
于 2010-05-04T17:06:39.613 回答