Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple inheritance with arguments

I have been reading quite a bit about inheritance, but I can't seem to grasp why this gives me an error (using Python 2.7.x).

class A(object):
    def __init__(self, value):
        super(A, self).__init__()
        print 'First %s' % value

class B(object):
    def __init__(self, value):
        super(B, self).__init__()
        print 'Second %s' % value

class Log(A, B):
    def __init__(self, a, b):
        A.__init__(self, a)
        B.__init__(self, b)

        print 'Log'




x = Log(1000, 2222)



// Error: __init__() takes exactly 2 arguments (1 given)
# Traceback (most recent call last):
#   File "<maya console>", line 21, in <module>
#   File "<maya console>", line 13, in __init__
#   File "<maya console>", line 3, in __init__
# TypeError: __init__() takes exactly 2 arguments (1 given) //
like image 311
arvidurs Avatar asked Mar 27 '15 22:03

arvidurs


1 Answers

Preface: my attempt to explain the MRO here is pretty deficient. If you have 45 minutes, this talk by Raymond Hettinger from PyCon 2015 does a much better job. Specifically, the idea of traversing "siblings", may be misleading. Instead, the super calls simply follow the MRO, (see help(Log)).

Despite the downvotes, this is actually a good question.

Consider the slightly modified code:

class A(object):
    def __init__(self, value):
        super(A, self).__init__()
        print 'A got: %s' % value

class B(object):
    def __init__(self, value):
        super(B, self).__init__()
        print 'B got: %s' % value

class Log(A, B):
    def __init__(self, a, b):
        A.__init__(self, a)
        B.__init__(self, b)

        print 'Log'

We can create instances of A and B without issue:

a = A("aa")  # A got: aa
b = B("bb")  # B got: bb

But when we try to create an instance of Log, we get an exception:

c = Log(123,456)
Traceback (most recent call last):
  File "temp2.py", line 21, in 
    c = Log(123, 456)
  File "temp2.py", line 13, in __init__
    A.__init__(self, a)
  File "temp2.py", line 3, in __init__
    super(A, self).__init__()
TypeError: __init__() takes exactly 2 arguments (1 given)

To try to figure out what's going on here, we can give a default to the value parameters (I use None):

class A(object):
    def __init__(self, value=None):
        super(A, self).__init__()
        print 'A got: %s' % value

class B(object):
    def __init__(self, value=None):
        super(B, self).__init__()
        print 'B got: %s' % value

class Log(A, B):
    def __init__(self, a, b):
        A.__init__(self, a)
        B.__init__(self, b)

        print 'Log'

Now our same code runs without error:

c = Log(123, 456)
B got: None
A got: 123
B got: 456
Log

But the output might confuse you: Why were 2 B instances created? or Why did specifying parameter defaults matter?

Well, consider the following (again, slightly modified) code:

class A(object):
    def __init__(self, value=None):
        print 'A got: %s' % value
        super(A, self).__init__()

class B(object):
    def __init__(self, value=None):
        print 'B got: %s' % value
        super(B, self).__init__()

class Log(A, B):
    def __init__(self, a, b):
        print("Before A")
        A.__init__(self, a)
        print("Before B")
        B.__init__(self, b)

        print 'Log'

Now, when we try to create our c object:

c = Log(123, 456)

We get:

Before A
A got: 123
B got: None
Before B
B got: 456
Log

What's happening here is that super(A, self).__init__() is actually calling B.__init__().

This is because super() will traverse siblings before the parent looking for someone to implement the method.

In this case, it find's B's __init__ method. B's __init__ method then also looks for siblings then parents, but since there are no siblings for B (as defined by the Log class -- which self is an instance of), B's __init__ calls object.__init__ which effectively does nothing.

Put another way (init being shorthand for __init__):

Log.init()
    A.init()
        super(A, self).init()      -->  B.init()
            super(B, self).init()  -->  object.init()
    B.init()
        super(B, self).init()      -->  object.init()

The reason the super inside A.init() finds B.init() (and not object.init() is because siblings are searched first. And in the context of self (Log(A,B)), B will be checked first, before the parent class.

This won't go the other direction as you might notice, so the super inside B.init() won't find A.init(), and instead finds object.init(). Again, this is because in the context of Log, B will be checked after A, followed by the parent class, object.

Some reading:

  • super() docs
  • Python's super() considered super!
  • Python 2.3 Method Resolution Order

EDIT: To fix this, you could call the superclass __init__ explicitly, instead of relying on super():

class A(object):
    def __init__(self, value):
        object.__init__(self)
        print 'First %s' % value

class B(object):
    def __init__(self, value):
        object.__init__(self)
        print 'Second %s' % value

class Log(A, B):
    def __init__(self, a, b):
        A.__init__(self, a)
        B.__init__(self, b)

        print 'Log'

x = Log(1000, 2222)

Or, since object.__init__() effectively does nothing, you're able to simply re-write your code as:

class A(object):
    def __init__(self, value):
        print 'First %s' % value

class B(object):
    def __init__(self, value):
        print 'Second %s' % value

class Log(A, B):
    def __init__(self, a, b):
        A.__init__(self, a)
        B.__init__(self, b)

        print 'Log'

x = Log(1000, 2222)

Both of which will output what (I think) you expected:

First 1000
Second 2222
Log
like image 142
jedwards Avatar answered Sep 28 '22 10:09

jedwards