带参数的多重继承 [英] Multiple inheritance with arguments
问题描述
我读了很多关于继承的书,但我似乎无法理解为什么这会给我一个错误(使用 Python 2.7.x).
A 类(对象):def __init__(self, value):super(A, self).__init__()打印第一个 %s"% 值B类(对象):def __init__(self, value):super(B, self).__init__()打印第二个 %s" % 值类日志(A,B):def __init__(self, a, b):A.__init__(self, a)B.__init__(self, b)打印日志"x = 日志(1000, 2222)//错误:__init__() 正好接受 2 个参数(给定 1 个)# 回溯(最近一次调用最后一次):# File "", line 21, in # 文件",第 13 行,在 __init__ 中# 文件",第 3 行,在 __init__ 中# TypeError: __init__() 需要 2 个参数(1 个给定)//
前言:我在这里解释 MRO 的尝试非常缺乏.如果您有 45 分钟的时间,PyCon 2015 的 Raymond Hettinger 的本次演讲做了一个好多更好的工作.具体来说,遍历兄弟姐妹"的想法可能会产生误导.相反,super
调用只是跟在 MRO 之后,(参见 help(Log)
).
尽管投了反对票,但这实际上是一个好问题.
考虑稍微修改的代码:
A 类(对象):def __init__(self, value):super(A, self).__init__()打印 'A got: %s' % 值B类(对象):def __init__(self, value):super(B, self).__init__()打印 'B got: %s' % 值类日志(A,B):def __init__(self, a, b):A.__init__(self, a)B.__init__(self, b)打印日志"
我们可以毫无问题地创建 A 和 B 的实例:
a = A("aa") # A得到:aab = B("bb") # B得到:bb
但是当我们尝试创建 Log 的实例时,我们得到了一个异常:
c = Log(123,456)
<前>回溯(最近一次调用最后一次):文件temp2.py",第 21 行,在c = 日志(123, 456)文件temp2.py",第 13 行,在 __init__ 中A.__init__(self, a)文件temp2.py",第 3 行,在 __init__ 中super(A, self).__init__()类型错误:__init__() 正好有 2 个参数(给定 1 个)
为了弄清楚这里发生了什么,我们可以给 value
参数一个默认值(我使用 None
):
A 类(对象):def __init__(self, value=None):super(A, self).__init__()打印 'A got: %s' % 值B类(对象):def __init__(self, value=None):super(B, self).__init__()打印 'B got: %s' % 值类日志(A,B):def __init__(self, a, b):A.__init__(self, a)B.__init__(self, b)打印日志"
现在我们相同的代码运行没有错误:
c = Log(123, 456)
<前>B得到:无A得到:123B得到:456日志
但输出可能会让您感到困惑:为什么创建了 2 个 B 实例? 或 为什么指定参数默认值很重要?
好吧,考虑以下(再次稍微修改)代码:
A 类(对象):def __init__(self, value=None):打印 'A got: %s' % 值super(A, self).__init__()B类(对象):def __init__(self, value=None):打印 'B got: %s' % 值super(B, self).__init__()类日志(A,B):def __init__(self, a, b):打印(A之前")A.__init__(self, a)打印(B之前")B.__init__(self, b)打印日志"
现在,当我们尝试创建我们的 c
对象时:
c = Log(123, 456)
我们得到:
<前>A前A得到:123B得到:无B前B得到:456日志这里发生的事情是 super(A, self).__init__()
实际上正在调用 B.__init__()
.
这是因为 super()
会在父级寻找实现该方法的人之前遍历兄弟姐妹.
在这种情况下,它会找到 B 的 __init__
方法.B 的 __init__
方法 then 也先寻找兄弟姐妹,然后是父母,但是因为 B 没有兄弟姐妹(由 Log
类定义——它self
是) 的一个实例,B 的 __init__
调用 object.__init__
有效地什么都不做.
换句话说(init
是 __init__
的简写):
Log.init()A.init()super(A, self).init() -->B.init()super(B, self).init() -->对象.init()B.init()super(B, self).init() -->对象.init()
A.init()
中的 super
找到 B.init()
(而不是 object.init()
是因为先搜索兄弟姐妹.而在 self
(Log(A,B)
) 的上下文中,B
将在父类之前先检查.
这不会像你可能注意到的那样走向另一个方向,所以 B.init()
中的 super
不会找到A.init()
,而是找到 object.init()
.同样,这是因为在 Log
的上下文中,B
将被检查 after A
,然后是父级类,object
.
一些阅读:
<小时>要解决此问题,您可以显式调用超类 __init__
,而不是依赖于 super()
:
A 类(对象):def __init__(self, value):object.__init__(self)打印第一个 %s"% 值B类(对象):def __init__(self, value):object.__init__(self)打印第二个 %s" % 值类日志(A,B):def __init__(self, a, b):A.__init__(self, a)B.__init__(self, b)打印日志"x = 日志(1000, 2222)
或者,由于 object.__init__()
实际上什么都不做,您可以简单地将代码重写为:
A 类(对象):def __init__(self, value):打印第一个 %s"% 值B类(对象):def __init__(self, value):打印第二个 %s" % 值类日志(A,B):def __init__(self, a, b):A.__init__(self, a)B.__init__(self, b)打印日志"x = 日志(1000, 2222)
两者都将输出您期望的(我认为):
<前>前 1000第二个2222日志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) //
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:
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
这篇关于带参数的多重继承的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!