调用 super().__init__(**kwargs) 和多重继承?
Calling super().__init__(**kwargs), and multiple inheritance?
我正在努力学习和理解如何在 Python 中使用 super,我一直在关注这本书 'Python journey from novice to expert',虽然我觉得我理解这个概念,但我在我的程序中执行 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', '', '30')
print(hourlystaff1)
print(hourlystaff1.get_rate())
returns 以下:
Bob Foo
Age: 23
Sex: M
DOB: 12/1/1980
The hourly rate of Bob is
None
这是我所期望的(我不确定为什么 '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('')
returns 这个错误:
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 有什么不同?
这也是来自同一本书和同一章节的综合案例研究。这对我有用,我明白它是如何工作的,但我似乎无法将它转化为我自己的作品。
这应该有效
class Employee:
def __init__(self, firstname='', lastname='', age='', dob=''):
self.firstname = firstname
self.lastname = lastname
self.age = age
self.dob = dob
那么你有childclass
class Hourly2(Employee):
def __init__(self,*args, **kwargs):
super(Hourly2,self).__init__(*args, **kwargs)
def get_rate(self,rate=None):
self.data=rate
print ('My hourly rate is {}'.format(self.data))
现在,让我们实例化
bob = Hourly2('Bob', 'Bar', '23', '12/1/2019')
我们可以检查属性
dir(bob)
['__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'age',
'dob',
'firstname',
'get_rate',
'lastname']
终于
bob.age
'23'
和
bob.get_rate('')
My hourly rate is
你在这里遇到的问题并不是 super 特有的,而是 kwargs 特有的。如果我们扔掉你的大部分代码并删除超级它看起来像这样:
class Hourly(Employee):
def __init__(self, rate=''):
self.rate = rate
some_crazy_function(**kwargs)
hourlystaff1 = Hourly('Bob', 'Foo', '23', 'M', '12/1/1980', '', '30')
有两个明显的问题:__init__
函数传递的参数比预期的要多,并且在 __init__
函数的主体中是对 kwargs
的引用,该引用未定义任何地方。虽然在这里理解 **kwargs
(及其兄弟 *args
)足以解决这里的问题,但 super 和 **kwargs
一起使用非常有用。让我们先看看为什么 super
有用。假设我们用一些很好的辅助方法围绕子进程编写了一些包装器(架构可能不是最适合这个问题的,但是只看到具有继承的动物也不是很有帮助。多重继承是一种非常罕见的情况,所以很难想出不是 Animals、GameEntities 或 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 - 我们可以直接使用 superclass 的名称并获得我们想要的行为。我们可以在此处重写以使用 super
但这不会改变行为。如果您只从一个 class 继承,您并不严格需要 super
,尽管它可以帮助您避免重复您继承的 class 名称。让我们添加到我们的 class 层次结构中,并包括从多个 class:
继承
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)
如果我们遵循 DownloadAndCheck
的初始化,我们会看到 Process.__init__
被调用了两次,一次通过 DownloadExecutableBefore.__init__
,一次通过 AuthenticationCheckerProcess.__init__
!所以我们要包装的进程也是运行两次,这不是我们想要的。在这个例子中,我们可以通过不在进程的初始化中调用 self.run()
来轻松解决这个问题,但在现实世界的情况下,这并不总是像这里那样容易解决。在这种情况下调用 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
将被传递给 all 初始化器,但实际上只有一个需要它。我们不能真的只将变量传递给需要它的函数(因为弄清楚那将是一场噩梦)但我们可以教其他 __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 应该抛出错误,参见 explenation of change of MRO algorithm)。 Mixin 甚至可能需要 super().__init__()
调用,尽管它们甚至不继承任何 class。总而言之,如果您使用多重继承,您的代码会变得非常复杂,因此如果您真的不需要它,通常最好考虑其他方法来为您的问题建模。
**kwargs
简单来说就是一个dict
class Employee:
def __init__(self, firstname='', lastname='', age='', dob='', **kwargs):
super().__init__(**kwargs)
构造函数 __init__
中的 **kwargs
意味着你不知道(不是不知道而是忽略)或者你不太关心 parent class Employee
。那么,python 如何知道要接受哪些关键字。这里的答案 super().__init__(**kwargs)
this **kwargs
具体表示 Employee
class 中的任何关键字都将被接受。这是逻辑, __init__()
中的 kwargs
必须存在,而 parent class 中实际上存在,因此, kwargs
必须是 super().__init__()
还有。
现在,让我们转到这行代码:
class Hourly(Employee):
def __init__(self, rate=''):
self.rate = rate
super().__init__(**kwargs)
__init__
不包含**kwargs
,所以super().__init__
怎么知道要继承什么参数。我们需要通过添加 **kwargs
来解决这个问题,所以代码将是这样的:
class Hourly(Employee):
def __init__(self, rate='', **kwargs):
self.rate = rate
super().__init__(**kwargs)
现在让我们谈谈争论:
__init__()
方法中有多少个args
?
--> 我们有两个位置参数(self
实例本身。第二个参数是 rate
,它采用默认值。
--> 而第三个arg
是kwargs
,所以这里必须传一个字典。这就是我开始说 kwargs
是 dict
:
的原因
我们来分析错误:
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
构造函数__init__()
接受一个或最多两个(为什么是1个或两个,因为一个参数有一个默认值(rate = ' '
)顺便说一句,默认值arg
不合理。但是我们提供了五个。
所以,让我们想办法只添加一个或两个(实例本身,或者当你想覆盖默认值时实例加上费率)并添加key-word arg
。我们可以通过两种方式做到这一点:
first:实例化的时候传一个dict
,但是开头要放**
:一定要按照写的一样传keys在 parent class.
bob = Hourly(rate='12%', **{'firstname':'Bar',
'lastname':'Bar', 'age':23, 'dob': '12/1/2019'})
第二个(整洁的):定义一个 dict_variable
然后在预先用 **
实例化时传递它。
personal_info = {'firstname':'Bar', 'lastname':'Bar', 'age':23, 'dob': '12/1/2019'}
# then pass it when like this
bob = Hourly(rate='12%', **personal_info)
最后:__str__()
具有 class 不存在的性别属性。要么在parentclass中加上那个。我认为这是显而易见的,我不打算谈论它。 (请注意,我从完整代码中排除了 __str__()
,但您应该在自己的代码中执行)
完整代码如下:
class Employee:
def __init__(self, firstname='', lastname='', age='', dob=''):
super().__init__(**kwargs)
self.firstname = firstname
self.lastname = lastname
self.age = age
self.dob = dob
class Hourly(Employee):
def __init__(self, rate='', **kwargs):
self.rate = rate
super().__init__(**kwargs)
def get_rate(self):
print('The hourly rate of {} is {}: '.format(self.firstname, self.rate))
personal_info = {'firstname':'Bar', 'lastname':'Bar', 'age':23, 'dob': '12/1/2019'}
bob = Hourly(rate='12%', **personal_info)
bob.get_rate()
这是结果
The hourly rate of Bob is: 12%
您也可以通过这种方式查看属性:
for key, val in bob.__dict__.items():
print(f'{key}: {val}')
得到结果
rate: 12%
firstname: Bob
lastname: Bar
age: 23
dob: 12/1/2019
我正在努力学习和理解如何在 Python 中使用 super,我一直在关注这本书 'Python journey from novice to expert',虽然我觉得我理解这个概念,但我在我的程序中执行 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', '', '30')
print(hourlystaff1)
print(hourlystaff1.get_rate())
returns 以下:
Bob Foo
Age: 23
Sex: M
DOB: 12/1/1980
The hourly rate of Bob is
None
这是我所期望的(我不确定为什么 '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('')
returns 这个错误:
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 有什么不同?
这也是来自同一本书和同一章节的综合案例研究。这对我有用,我明白它是如何工作的,但我似乎无法将它转化为我自己的作品。
这应该有效
class Employee:
def __init__(self, firstname='', lastname='', age='', dob=''):
self.firstname = firstname
self.lastname = lastname
self.age = age
self.dob = dob
那么你有childclass
class Hourly2(Employee):
def __init__(self,*args, **kwargs):
super(Hourly2,self).__init__(*args, **kwargs)
def get_rate(self,rate=None):
self.data=rate
print ('My hourly rate is {}'.format(self.data))
现在,让我们实例化
bob = Hourly2('Bob', 'Bar', '23', '12/1/2019')
我们可以检查属性
dir(bob)
['__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'age',
'dob',
'firstname',
'get_rate',
'lastname']
终于
bob.age
'23'
和
bob.get_rate('')
My hourly rate is
你在这里遇到的问题并不是 super 特有的,而是 kwargs 特有的。如果我们扔掉你的大部分代码并删除超级它看起来像这样:
class Hourly(Employee):
def __init__(self, rate=''):
self.rate = rate
some_crazy_function(**kwargs)
hourlystaff1 = Hourly('Bob', 'Foo', '23', 'M', '12/1/1980', '', '30')
有两个明显的问题:__init__
函数传递的参数比预期的要多,并且在 __init__
函数的主体中是对 kwargs
的引用,该引用未定义任何地方。虽然在这里理解 **kwargs
(及其兄弟 *args
)足以解决这里的问题,但 super 和 **kwargs
一起使用非常有用。让我们先看看为什么 super
有用。假设我们用一些很好的辅助方法围绕子进程编写了一些包装器(架构可能不是最适合这个问题的,但是只看到具有继承的动物也不是很有帮助。多重继承是一种非常罕见的情况,所以很难想出不是 Animals、GameEntities 或 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 - 我们可以直接使用 superclass 的名称并获得我们想要的行为。我们可以在此处重写以使用 super
但这不会改变行为。如果您只从一个 class 继承,您并不严格需要 super
,尽管它可以帮助您避免重复您继承的 class 名称。让我们添加到我们的 class 层次结构中,并包括从多个 class:
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)
如果我们遵循 DownloadAndCheck
的初始化,我们会看到 Process.__init__
被调用了两次,一次通过 DownloadExecutableBefore.__init__
,一次通过 AuthenticationCheckerProcess.__init__
!所以我们要包装的进程也是运行两次,这不是我们想要的。在这个例子中,我们可以通过不在进程的初始化中调用 self.run()
来轻松解决这个问题,但在现实世界的情况下,这并不总是像这里那样容易解决。在这种情况下调用 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
将被传递给 all 初始化器,但实际上只有一个需要它。我们不能真的只将变量传递给需要它的函数(因为弄清楚那将是一场噩梦)但我们可以教其他 __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 应该抛出错误,参见 explenation of change of MRO algorithm)。 Mixin 甚至可能需要 super().__init__()
调用,尽管它们甚至不继承任何 class。总而言之,如果您使用多重继承,您的代码会变得非常复杂,因此如果您真的不需要它,通常最好考虑其他方法来为您的问题建模。
**kwargs
简单来说就是一个dict
class Employee:
def __init__(self, firstname='', lastname='', age='', dob='', **kwargs):
super().__init__(**kwargs)
构造函数 __init__
中的 **kwargs
意味着你不知道(不是不知道而是忽略)或者你不太关心 parent class Employee
。那么,python 如何知道要接受哪些关键字。这里的答案 super().__init__(**kwargs)
this **kwargs
具体表示 Employee
class 中的任何关键字都将被接受。这是逻辑, __init__()
中的 kwargs
必须存在,而 parent class 中实际上存在,因此, kwargs
必须是 super().__init__()
还有。
现在,让我们转到这行代码:
class Hourly(Employee):
def __init__(self, rate=''):
self.rate = rate
super().__init__(**kwargs)
__init__
不包含**kwargs
,所以super().__init__
怎么知道要继承什么参数。我们需要通过添加 **kwargs
来解决这个问题,所以代码将是这样的:
class Hourly(Employee):
def __init__(self, rate='', **kwargs):
self.rate = rate
super().__init__(**kwargs)
现在让我们谈谈争论:
__init__()
方法中有多少个args
?
--> 我们有两个位置参数(self
实例本身。第二个参数是 rate
,它采用默认值。
--> 而第三个arg
是kwargs
,所以这里必须传一个字典。这就是我开始说 kwargs
是 dict
:
我们来分析错误:
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
构造函数__init__()
接受一个或最多两个(为什么是1个或两个,因为一个参数有一个默认值(rate = ' '
)顺便说一句,默认值arg
不合理。但是我们提供了五个。
所以,让我们想办法只添加一个或两个(实例本身,或者当你想覆盖默认值时实例加上费率)并添加key-word arg
。我们可以通过两种方式做到这一点:
first:实例化的时候传一个dict
,但是开头要放**
:一定要按照写的一样传keys在 parent class.
bob = Hourly(rate='12%', **{'firstname':'Bar',
'lastname':'Bar', 'age':23, 'dob': '12/1/2019'})
第二个(整洁的):定义一个 dict_variable
然后在预先用 **
实例化时传递它。
personal_info = {'firstname':'Bar', 'lastname':'Bar', 'age':23, 'dob': '12/1/2019'}
# then pass it when like this
bob = Hourly(rate='12%', **personal_info)
最后:__str__()
具有 class 不存在的性别属性。要么在parentclass中加上那个。我认为这是显而易见的,我不打算谈论它。 (请注意,我从完整代码中排除了 __str__()
,但您应该在自己的代码中执行)
完整代码如下:
class Employee:
def __init__(self, firstname='', lastname='', age='', dob=''):
super().__init__(**kwargs)
self.firstname = firstname
self.lastname = lastname
self.age = age
self.dob = dob
class Hourly(Employee):
def __init__(self, rate='', **kwargs):
self.rate = rate
super().__init__(**kwargs)
def get_rate(self):
print('The hourly rate of {} is {}: '.format(self.firstname, self.rate))
personal_info = {'firstname':'Bar', 'lastname':'Bar', 'age':23, 'dob': '12/1/2019'}
bob = Hourly(rate='12%', **personal_info)
bob.get_rate()
这是结果
The hourly rate of Bob is: 12%
您也可以通过这种方式查看属性:
for key, val in bob.__dict__.items():
print(f'{key}: {val}')
得到结果
rate: 12%
firstname: Bob
lastname: Bar
age: 23
dob: 12/1/2019