views:

312

answers:

2

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
+3  A: 

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
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.
Casey
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
See my update, super executes all \__init\__ methods.
Casey
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
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.
Casey
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
A: 

"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
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.
Casey
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