python - 调用 super().__init__(**kwargs) 和多重继承?
问题描述
我正在尝试学习和理解如何在 Python 中使用 super,我一直在关注《Python 从新手到专家的旅程》一书,尽管我觉得我理解了这个概念,但我在自己的代码中执行 super 时遇到了问题。
例如,此方法适用于我:
class Employee:
def __init__(self, firstname, lastname, age, sex, dob):
self.firstname = firstname
self.lastname = lastname
self.age = age
self.sex = sex
self.dob = dob
self.all_staff.append(self)
class Hourly(Employee):
def __init__(self, firstname, lastname, age, sex, dob, rate, hours):
self.rate = rate
self.hours = hours
super().__init__(firstname, lastname, age, sex, dob)
def __str__(self):
return "{} {}\nAge: {}\nSex: {}\nDOB: {}\n".format(self.firstname, self.lastname, self.age,
self.sex, self.dob)
def get_rate(self):
print('The hourly rate of {} is {} '.format(self.firstname, self.rate))
hourlystaff1 = Hourly('Bob', 'Foo', '23', 'M', '12/1/1980', '$15', '30')
print(hourlystaff1)
print(hourlystaff1.get_rate())
返回以下内容:
Bob Foo
Age: 23
Sex: M
DOB: 12/1/1980
The hourly rate of Bob is $15
None
这是我所期望的(我不确定为什么还会返回“无”,也许有人可以解释一下?)。
然后我想尝试使用 super 但使用 **kwargs 像这样:
class Employee:
def __init__(self, firstname='', lastname='', age='', dob='', **kwargs):
super().__init__(**kwargs)
self.firstname = firstname
self.lastname = lastname
self.age = age
self.dob = dob
class Hourly(Employee):
def __init__(self, rate=''):
self.rate = rate
super().__init__(**kwargs)
def __str__(self):
return "{} {}\nAge: {}\nSex: {}".format(self.firstname, self.lastname, self.age,
self.sex, self.dob, self.rate)
def get_rate(self):
print('The hourly rate of {} is {} '.format(self.firstname, self.rate))
bob = Hourly('Bob', 'Bar', '23', '12/1/2019')
bob.get_rate('$12')
返回此错误:
File "staff_b.py", line 33, in <module>
bob = Hourly('Bob', 'Bar', '23', '12/1/2019')
TypeError: __init__() takes from 1 to 2 positional arguments but 5 were given
在第二种方法中我做错了什么?我怎样才能在这里正确使用 **kwargs 和 super ?
编辑:
这是我一直在关注的书中示例的屏幕截图:
我在第二个示例中使用 **kwargs 和 super 的方式有什么不同?
这也是来自同一本书和同一章的综合案例研究。这对我有用,我理解它是如何工作的,但我似乎无法将它转化为我自己的作品。
解决方案
你在这里遇到的问题并不是超级特定的,而是更特定于 kwargs。如果我们把你的大部分代码扔掉并删除 super 它看起来像这样:
class Hourly(Employee):
def __init__(self, rate=''):
self.rate = rate
some_crazy_function(**kwargs)
hourlystaff1 = Hourly('Bob', 'Foo', '23', 'M', '12/1/1980', '$15', '30')
有两个明显的问题:__init__
函数传递的参数比预期的要多,函数体中有一个没有在任何地方定义__init__
的引用。kwargs
虽然这里的理解**kwargs
(及其兄弟*args
)足以解决这里的问题,并且**kwargs
非常有用。让我们先看看为什么super
有用。让我们想象一下,我们用一些很好的辅助方法围绕子流程编写了一些包装器(架构可能不是最适合这个问题,但只看到具有继承的动物也不是很有帮助。多重继承是一种非常罕见的情况,所以很难想出不是动物、游戏实体或 GUIwidgets 的好例子):
class Process:
def __init__(self, exe):
self.exe = exe
self.run()
class DownloadExecutableBeforeProcess(Process):
def __init__(self, exe):
self.download_exe(exe)
Process.__init__(self, exe)
在这里我们进行继承,我们甚至不需要使用 super - 我们可以显式地使用超类的名称并拥有我们想要的行为。我们可以重写以super
在这里使用,但它不会改变行为。如果你只从一个你不需要super
的类继承,虽然它可以帮助你不重复你继承的类名。让我们添加到我们的类层次结构中并包括从多个类继承:
class AuthenticationCheckerProcess(Process):
def __init__(self, exe, use_sha=True):
self.check_if_authorized(exe, use_sha)
Process.__init__(self, exe)
class DownloadAndCheck(DownloadExecutableBefore, AuthenticationCheckerProcess):
def __init__(self, exe):
DownloadExecutableBefore.__init__(exe)
AuthenticationCheckerProcess.__init__(exe, use_sha=False)
如果我们遵循 init ,DownloadAndCheck
我们会看到它Process.__init__
被调用了两次,一次通过DownloadExecutableBefore.__init__
,一次通过AuthenticationCheckerProcess.__init__
!所以我们要包装的进程也运行了两次,这不是我们想要的。在这个例子中,我们可以通过不调用self.run()
进程的 init 来轻松解决这个问题,但在现实世界的情况下,这并不总是像这里那样容易修复。在这种情况下,调用Process.__init__
似乎是错误的。我们能以某种方式解决这个问题吗?
class DownloadAndCheck(DownloadExecutableBefore, AuthenticationCheckerProcess):
def __init__(self, exe):
super().__init__(exe, use_sha=False)
# also replace the Process.__init__ cals in the other classes with super
super
解决了这个问题,并且只会调用Process.__init__
一次。它还会处理函数运行的顺序,但这不是一个大问题。我们还有一个问题:use_sha=False
将被传递给所有的初始化器,但实际上只有一个需要它。我们实际上不能只将变量传递给需要它的函数(因为弄清楚这将是一场噩梦),但我们可以教其他__init__
s 忽略键盘:
class Process:
def __init__(self, exe, **kwargs):
# accept arbitrary keywoards but ignore everything but exe
# also put **kwargs in all other initializers
self.exe = exe
self.run()
class DownloadExecutableBeforeProcess(Process):
def __init__(self, exe, **kwargs):
self.download_exe(exe)
# pass the keywoards into super so that other __init__s can use them
Process.__init__(self, exe, **kwargs)
现在super().__init__(exe, use_sha=False)
调用将成功,每个初始化程序只使用它理解的关键字,并简单地将其他关键字向下传递。
因此,如果您有多重继承并使用不同的(关键字)参数 super 和 kwargs 可以解决您的问题。但是超级继承和多重继承很复杂,特别是如果你有比这里更多的继承层。有时甚至没有定义函数应该被调用的顺序(然后python应该抛出一个错误,参见例如改变MRO算法的解释)。Mixins 甚至可能需要super().__init__()
调用,尽管它们甚至不从任何类继承。总而言之,如果您使用多重继承,您的代码会变得非常复杂,所以如果您真的不需要它,通常最好考虑其他方法来为您的问题建模。