8

I've seen a few questions on this topic, but I haven't been able to find a definitive answer.

I would like to know the proper way to use old-style classes in a new Python code base. Let's say for example that I have two fixed classes, A and B. If I want to subclass A and B, and convert to new-style classes (A2 and B2), this works. However there is an issue if I want to create a new class C, from A2 and B2.

Therefore, is it possible to continue with this method, or do all classes have to conform to the old-style if any base class is defined as old-style?

See the example code for clarification:

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

The output of this code:

class A
class A2
---
class B
class B2
---
class A
class A2
class C

As you can see, the problem is that in the call to C(), class B2 was never initialized.


Update - New-Style Class Example

I guess it is not clear what the correct initialization sequence should be when using super. Here is a working example where a call to super does initialize all base classes, not just the first one it finds.

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

and produces the output:

class B
class B2
class A
class A2
class C
cmcginty
  • 113,384
  • 42
  • 163
  • 163
  • Updated answer to include explanation of diamond inheritance and the problem solved with new-style classes. – Lennart Regebro Sep 15 '09 at 10:40
  • 1
    Why do you even have old-style classes in the first place? – S.Lott Sep 15 '09 at 10:52
  • It's Python. You have the source. You should be able to fix this without working around it. – S.Lott Sep 15 '09 at 14:52
  • 1
    Well, true, but then you have to fix it again when the author updates the third-party module. Besides, he doesn't actually have a problem. :) – Lennart Regebro Sep 15 '09 at 15:51
  • Your new style example is not equivalent to your old style example. In the new style example, all involved init's call another init (up until they reach class B, where there is no more super class to call. In the old style example, class A and B does not call other inits. That's your error. If you want to have all inits called with old style classes you need to be explicit, as already mentioned. If you want super to work so you don't have to be explicit, you need to use only new style classes. – Lennart Regebro Sep 15 '09 at 20:01
  • Sorry you feel that way, but that is the proper way to use super(). Just because the old-style classes don't have an equivalent form, should not be a reason to discount this type of comparison. – cmcginty Sep 15 '09 at 21:35
  • "Feel"? Comparison? You asked a question you have gotten answers. If something is still unclear, please let me know. – Lennart Regebro Sep 15 '09 at 22:07
  • Please correct me if wrong, but your answer is then "don't use super."? – cmcginty Sep 15 '09 at 23:10
  • No, the answer is that if you need to use old-style classes, the call the classes explicitly. Old-style classes don't have super so you can't use it anyway. Or, you use new-style classes. The you can use super. My experience with this is that super is going to get confusing, and it's better to call the super-classes explicitly even with new-style classes, but that's a matter of taste. – Lennart Regebro Sep 16 '09 at 05:26
  • I was extremely confused about the behavior shown in your new-style example. But then I found an article that explained it. See [this answer](http://stackoverflow.com/a/12306773/498594). – Kelvin Sep 06 '12 at 19:10
  • "I would like to know the proper way to use old-style classes in a new Python code base." Fix them to be new-style. – S.Lott Sep 16 '09 at 19:42
  • 1
    This was mentioned before, but it is painful to maintain a customized version of 3rd-party libraries. If there is any way to avoid this, then I would prefer that. – cmcginty Sep 16 '09 at 23:18
  • Precisely how many classes have this problem? Who is the current maintainer? Please be specific about the expected workload. Generally, old-style classes are unmaintained libraries. There will be no maintenance except for your fix. And. You have the source. You do not *need* to coordinate your updates with anyone. Just make the change. – S.Lott Sep 17 '09 at 00:24

1 Answers1

7

This is not a issue of mixing old and new style classes. super() does not call all base classes functions, it calls the first one it finds according the method resolution order. In this case A2, which in turn calls A.

If you want to call both, do so explicitly:

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

That should solve it.

Update:

The diamond inheritance problem as you refer to, is the question of which class to call in a diamond inheritance situation, like this:

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

Now test this out:

>>> D().method1()
'class B'

This is correct. It calls the first class' implementation. However, let's try this with method2:

>>> D().method2()
'class A'

Oups, WRONG! It should have called class C.method2() here, because even though class B does not override method2, class C does. Now make class A a newstyle class:

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

And try again:

>>> D().method1()
'class B'
>>> D().method2()
'class C'

and hey presto, it works. This is the method resolution order difference between new and old-style classes, and this is what sometimes makes it confusing to mix them.

Notice how at no point both B and C gets called. This is true even if we call super.

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'

If you want to call both B and C, you MUST call both explicitly.

Now if you unbreak the diamond, like in your example having separate base classes, the result is different:

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'

This is also per design. Still nowhere two base classes gets called. If you want that to happen you still have to call both explicitly.

Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251
  • Super will call all base class methods if implemented with all new-style classes and used consistently. My question is how to handle the case when you are forced to mix in an old-style class. I am aware of how to directly call the parent methods, but this breaks for diamond inheritance. – cmcginty Sep 15 '09 at 09:18
  • No it won't. If you make A and B new-style classes, you get exactly the same result as above. And I don't see how it would break break diamond inheritance to directly call the call the parent methods. I suspect you have the diamond inheritance problem slightly backwards. In fact, you seem to assume that diamond inheritance should call both base classes (which it typically does not) and the only way to do that in Python is to call them explicitly. – Lennart Regebro Sep 15 '09 at 10:05
  • 1
    No it doesn't. It executes *one*. But in your new example, that one calls another one, that calls another one that calls another one, etc, until all have been called. Since old-style classes doesn't have super, obviously you can't do it with super. So again: You need to be explicit. – Lennart Regebro Sep 15 '09 at 19:48
  • Getting pedantic that the initial call to super() does not directly call all base classes is really irrelevant. For the average developer, the important point is that super() works when implemented properly. – cmcginty Sep 15 '09 at 21:36
  • Of course it works. It just does not do what you initially asked: Call all base classes. It calls *one* base class. You only get a call to all base classes if all bases classes uses super(), and hence they call one each. There is nothing "properly" about this. – Lennart Regebro Sep 15 '09 at 22:04
  • This answer is way too complicated for this question. In case of multiple inheritance, super() simply expects all upstream classes to "cooperate" by also using super(). If there's an old-style class in the hierarchy, it simply breaks the chain. Therefore, I would argue that it is correct to say that super() calls the methods of ALL parent classes, but in a (cooperative) chain, which, as I mentioned, old-style classes in the hierarchy break. – Erik Kaplun Oct 22 '11 at 15:13
  • 3
    No, the answer is exhaustive. It's not complicated at all. And super() does not call all the methods of all the parent classes, not even in a cooperative chain. Claiming so is confusing, complicated and in fundamental ways incorrect. – Lennart Regebro Oct 22 '11 at 16:39