【问题标题】:Implicitly invoking parent class initializer隐式调用父类初始化器
【发布时间】:2010-03-01 09:17:57
【问题描述】:
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

在调用超级构造函数而不命名当前类(Bsuper(B, self).__init__(1, b, c))?

【问题讨论】:

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


    【解决方案1】:

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

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

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

    【讨论】:

    • 我开始怀疑初始化签名和多重继承
    【解决方案2】:

    您的代码与方法解析顺序无关。方法解析出现在多重继承的情况下,而您的示例并非如此。您的代码完全是错误的,因为您假设 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】:

      也许您正在寻找的是元类?

      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 的所有基类

      【讨论】:

      • 我刚刚意识到我正在用大炮射击麻雀(丹麦谚语),为此我很抱歉,但是除非有人决定删除它,否则我会让它成为现实。
      • 我宁愿不要为了一些微不足道的事情而求助于元类,但是谢谢
      【解决方案4】:

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

      给定类定义中的方法总是破坏双下划线属性以包含定义它们的类的名称。因此,如果您以名称破坏的形式存储对类的引用,实例可以看到它,你可以在调用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>
      

      【讨论】:

        猜你喜欢
        • 2023-03-13
        • 2010-10-28
        • 2018-12-22
        • 1970-01-01
        • 2021-03-17
        • 1970-01-01
        • 2021-08-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多