如何混合使用旧式和新式Python类?

8

我看到了一些关于这个话题的问题,但是我没有找到一个明确的答案。

我想知道在新 Python 代码库中如何正确使用旧风格类。例如,假设我有两个固定的类 AB。如果我想要继承 AB,并转换为新风格类 (A2B2),那么这是可以的。然而,如果我想要创建一个新的类 C,它来自于 A2B2,就会出现问题。

因此,是否有可能继续使用这种方法,或者所有的类都必须符合旧风格,只要有任何一个基类被定义为旧风格?

请参考以下示例代码以获得更清晰的理解:

class A:
   def __init__(self):
      print 'class A'

class B:
   def __init__(self):
      print 'class B'

class A2(A,object):
   def __init__(self):
      super(A2, self).__init__()
      print 'class A2'

class B2(B,object):
   def __init__(self):
      super(B2, self).__init__()
      print 'class B2'

class C(A2, B2):
   def __init__(self):
      super(C,self).__init__()
      print 'class C'

A2()
print '---'
B2()
print '---'
C()
这段代码的输出:
class A
class A2
---
class B
class B2
---
class A
class A2
class C
如您所见,问题在于调用C()时,类B2从未被初始化。
更新 - 新式类示例 我猜当使用super时,正确的初始化顺序并不清楚。这里有一个工作示例,其中对super的调用确实初始化了所有基类,而不仅仅是找到的第一个基类。
class A(object):
   def __init__(self):
      super(A, self).__init__()
      print 'class A'

class B(object):
   def __init__(self):
      super(B, self).__init__()
      print 'class B'

class A2(A):
   def __init__(self):
      super(A2, self).__init__()
      print 'class A2'

class B2(B):
   def __init__(self):
      super(B2, self).__init__()
      print 'class B2'

class C(A2, B2):
   def __init__(self):
      super(C, self).__init__()
      print 'class C'

C()
并生成输出:
class B
class B2
class A
class A2
class C

1
你为什么一开始就要使用旧式类呢? - S.Lott
1
嗯,没错,但是当作者更新第三方模块时,你又得再次修复它。此外,他实际上并没有问题。 :) - Lennart Regebro
我想了解在新的Python代码库中使用旧式类的正确方法。将它们修复为新式类。 - S.Lott
1
这个之前提到过,但是维护一个定制化的第三方库版本非常麻烦。如果有任何避免这种情况的方法,我会更倾向于使用它们。 - cmcginty
这个问题涉及多少个类?现在的维护者是谁?请具体说明预期工作量。通常,旧式类是未维护的库。除了你的修复之外,将不会进行任何维护。而且,你有源代码。你不需要与任何人协调更新。只需进行更改即可。 - S.Lott
显示剩余8条评论
1个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
7
这不是混合使用旧式类和新式类的问题。super()并没有调用所有基类函数,它只会调用第一个按照方法解析顺序找到的函数。在这种情况下,它找到了A2,然后A2又调用了A。 如果你想要调用两个函数,需要显式地这么做:
class C(A2, B2):
   def __init__(self):
      A2.__init__(self)
      B2.__init__(self)
      print 'class C'
这应该解决了。 更新: 您所指的“钻石继承问题”是在钻石继承情况下需要调用哪个类的问题,类似于这样:
class A:
   def method1(self):
      print 'class A'

   def method2(self):
      print 'class A'

class B(A):
   def method1(self):
      print 'class B'

class C(A):
   def method1(self):
      print 'class C'

   def method2(self):
      print 'class C'

class D(B, C):
   pass
现在试一下这个:
>>> D().method1()
'class B'
这是正确的。它调用了第一个类的实现。 然而,让我们尝试使用method2:
>>> D().method2()
'class A'
哎呀,错了!这里应该调用class C的method2()方法,因为即使class B没有重写method2,class C也重写了。现在将class A变成新式类:
class A(object):
   def method1(self):
      print 'class A'
再试一次:
>>> D().method1()
'class B'
>>> D().method2()
'class C'
“咔嚓”一声,它就可以工作了。这是新式类和旧式类之间的方法解析顺序差异,这也是有时混合使用它们会令人困惑的原因。 请注意,即使我们调用super,B和C也不会在任何时候都被调用。
class D(B, C):
   def method1(self):
      super(D, self).method1()

   def method2(self):
      super(D, self).method2()

>>> D().method1()
'class B'
>>> D().method2()
'class C'
如果您想同时调用B和C,则必须明确地调用两者。

现在,如果您解开钻石,就像在您的示例中有不同的基类一样,结果是不同的:

class A1(object):
   def method1(self):
      print 'class A1'

   def method2(self):
      print 'class A1'

class A2(object):
   def method1(self):
      print 'class A2'

   def method2(self):
      print 'class A2'

class B(A1):
   def method1(self):
      print 'class B'

class C(A2):
   def method1(self):
      print 'class C'

   def method2(self):
      print 'class C'

class D(B, C):
   def method1(self):
      super(D, self).method1()

   def method2(self):
      super(D, self).method2()


>>> D().method1()
'class B'
>>> D().method2()
'class A1'
这也是按照设计的。仍然没有两个基类被调用。如果你想要发生这种情况,你仍然需要明确地调用两个基类。

如果使用所有新式类并且保持一致,Super将调用所有基类方法。我的问题是如何处理当你被迫混合使用旧式类的情况。我知道如何直接调用父类方法,但这会在钻石继承中出现问题。 - cmcginty
不,不会的。如果你将A和B定义为新式类,你会得到与上面完全相同的结果。而且我不明白直接调用父类方法会如何破坏钻石继承。我怀疑你对钻石继承问题有些误解。事实上,你似乎认为钻石继承应该调用两个基类(通常并不是这样),在Python中唯一的方法就是显式地调用它们。 - Lennart Regebro
1
不,它只执行一个。但在您的新示例中,该函数调用另一个函数,该函数又调用另一个函数,以此类推,直到所有函数都被调用。由于旧式类没有super,显然您无法使用super来实现。所以再次强调:您需要明确指出。 - Lennart Regebro
这个答案对于这个问题来说太复杂了。在多重继承的情况下,super()只是期望所有上游类也使用super()来“合作”。如果在继承层次结构中有一个旧式类,它就会简单地打破这个链条。因此,我认为可以说super()调用所有父类的方法,但是在一个(合作的)链条中,而旧式类在继承层次结构中会打破这个链条。 - Erik Kaplun
3
不,答案是详尽的。这并不复杂。而且super()也不会调用所有父类的方法,甚至不会在合作链中调用所有的方法。声称如此是令人困惑、复杂和基本上不正确的。 - Lennart Regebro
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,