Multiple inheritance with arguments

社会主义新天地 提交于 2020-06-27 10:02:12

问题


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

回答1:


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


来源:https://stackoverflow.com/questions/29311504/multiple-inheritance-with-arguments

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!