调用 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 有什么不同?

这也是来自同一本书和同一章节的综合案例研究。这对我有用,我明白它是如何工作的,但我似乎无法将它转化为我自己的作品。

https://pastebin.com/NYGJfMik

这应该有效

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,它采用默认值。 --> 而第三个argkwargs,所以这里必须传一个字典。这就是我开始说 kwargsdict:

的原因

我们来分析错误:

 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