简体   繁体   English

调用 super().__init__(**kwargs) 和多重继承?

[英]Calling super().__init__(**kwargs), and multiple inheritance?

I'm trying to learn and understand how to use super in Python, Ive been following the book 'Python journey from novice to expert' and although I feel that I understand the concept Im having problems executing super in my own code.我正在尝试学习和理解如何在 Python 中使用 super,我一直在关注《Python 从新手到专家的旅程》一书,尽管我觉得我理解了这个概念,但我在自己的代码中执行 super 时遇到了问题。

For example, this method works for me:例如,此方法适用于我:

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

returns the following:返回以下内容:

Bob Foo
Age: 23
Sex: M
DOB: 12/1/1980

The hourly rate of Bob is $15 
None

This is what I expected (I'm not sure why 'None' is also being returned though, perhaps someone can explain?).这是我所期望的(我不确定为什么还会返回“无”,也许有人可以解释一下?)。

And then I wanted to try this using super but with **kwargs like so:然后我想尝试使用 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')

returns this error:返回此错误:

  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

what am I doing wrong in this second approach?在第二种方法中我做错了什么? How can I use **kwargs and super correctly here?我怎样才能在这里正确使用 **kwargs 和 super ?

Edit:编辑:

this is a screenshot of an example from the book which I have been following:这是我一直在关注的书中示例的屏幕截图:

在此处输入图像描述

what is different between how I use **kwargs and super in my second example to there?我在第二个示例中使用 **kwargs 和 super 的方式有什么不同?

This is also a comprehensive case study from the same book and chapter.这也是来自同一本书和同一章的综合案例研究。 This works for me and I understand how it works but I dont seem to be able to translate it into my own work.这对我有用,我理解它是如何工作的,但我似乎无法将它转化为我自己的作品。

https://pastebin.com/NYGJfMik https://pastebin.com/NYGJfMik

The poblem you have here isn't really specific to super but more specific to kwargs.你在这里遇到的问题并不是超级特定的,而是更特定于 kwargs。 If we throw most of your code away and remove the super it looks like this:如果我们把你的大部分代码扔掉并删除 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')

There are two obvious problems: The __init__ function is getting more arguments passed than expected and in the body of the __init__ function is a reference to kwargs which is not defined anywhere.有两个明显的问题: __init__函数传递的参数比预期的要多,并且在__init__函数的主体中是对未在任何地方定义的kwargs的引用。 While here understanding **kwargs (and its sibling *args ) is enough to fix the problem here super and **kwargs are very useful together.虽然在这里理解**kwargs (及其兄弟*args )足以解决这里的问题 super 和**kwargs一起非常有用。 Lets first look why super is useful.让我们先看看为什么super有用。 Lets imagine we write some wrappers around subprocesses with some nice helper methods (the architecture is maybe not the best fit for the problem, but only ever seeing animals with inheritance is also not super helpful. Multiple inheritance is a really rare case, so it's hard to come up with good examples that are not Animals, GameEntities or GUIwidgets):让我们想象一下,我们用一些不错的辅助方法围绕子进程编写了一些包装器(架构可能不是最适合这个问题,但只看到具有继承的动物也不是很有帮助。多重继承是一种非常罕见的情况,所以很难想出不是动物、游戏实体或 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)

Here we are doing inheritance and we do not even need to use super - we can just use the name of the superclass explicitly and have the behavior we want.在这里我们进行继承,我们甚至不需要使用 super - 我们可以显式地使用超类的名称并拥有我们想要的行为。 We could rewrite to use super here but it would not change the behavior.我们可以在这里重写以使用super但它不会改变行为。 If you only inherit from one class you don't strictly need super , although it can help you to not repeat the classname you inherit from.如果您只从一个类继承,则不需要super ,尽管它可以帮助您不重复继承自的类名。 Lets add to our class hirarchy and include inherting from more than one 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)

If we follow the init of DownloadAndCheck we see that Process.__init__ is called twice, once through DownloadExecutableBefore.__init__ and once through AuthenticationCheckerProcess.__init__ !如果我们遵循DownloadAndCheck的 init,我们会看到Process.__init__被调用了两次,一次通过DownloadExecutableBefore.__init__和一次通过AuthenticationCheckerProcess.__init__ So our process we want to wrap is also run twice, which is not what we want.所以我们要包装的进程也运行了两次,这不是我们想要的。 Here in this example we could fix this easily by not calling self.run() in the init of process, but in realworld cases this is not always so easy to fix like here.在这个例子中,我们可以通过在进程的 init 中不调用self.run()来轻松解决这个问题,但在现实世界的情况下,这并不总是像这里那样容易修复。 Calling Process.__init__ just seems wrong in this case.在这种情况下,调用Process.__init__似乎是错误的。 Can we somehow fix this?我们能以某种方式解决这个问题吗?

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 fixes this problems and will only call Process.__init__ once. super修复了这个问题,并且只会调用Process.__init__一次。 It will also take care of the order in which the function should run, but this is not a big problem here.它还会处理函数运行的顺序,但这不是一个大问题。 We still have a problem: use_sha=False will get passed to all initializers, but only one actually needs it.我们还有一个问题: use_sha=False会被传递给所有的初始化器,但实际上只有一个需要它。 We can't really only pass the variable to only the functions that need it (because figuring that out would be a nightmare) but we can teach the other __init__ s to just ignore the keywoard:我们实际上不能只将变量传递给需要它的函数(因为弄清楚这将是一场噩梦),但我们可以教其他__init__忽略键盘:

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)

Now the super().__init__(exe, use_sha=False) call will succeed, each initializer only takes the keywoards it understands and simply passes the others further down.现在super().__init__(exe, use_sha=False)调用将成功,每个初始化程序只使用它理解的关键字,并简单地将其他的向下传递。

So if you have mutliple inheritance and use different (keywoard) arguments super and kwargs can solve your problem.因此,如果您有多重继承并使用不同的(关键字)参数 super 和 kwargs 可以解决您的问题。 But super and multiple inheritance is complicated, especially if you have more inheritance layers than here.但是超级继承和多重继承很复杂,特别是如果你有比这里更多的继承层。 Sometimes the order in which functions should be calles is not even defined (and python should throw an error then, see eg explenation of change of MRO algorithm ).有时甚至没有定义函数应该被调用的顺序(然后python应该抛出一个错误,参见例如改变MRO算法的解释)。 Mixins might even require a super().__init__() call although they don't even inherit from any class. Mixins 甚至可能需要super().__init__()调用,尽管它们甚至不从任何类继承。 All in all you gain a lot of complexity in your code if you use multiple inheritance, so if you don't really need it, it's often better to think of other ways to model your problem.总而言之,如果您使用多重继承,您的代码会变得非常复杂,因此如果您真的不需要它,通常最好考虑其他方法来为您的问题建模。

**kwargs in the simples words is a dict **kwargs是一个dict

class Employee:
    def __init__(self, firstname='', lastname='', age='', dob='', **kwargs):
        super().__init__(**kwargs)

the **kwargs in the constructor __init__ , means you don't know (not in the meaning of not knowing but sort of ignoring) or you care less about the arguments in the parent class Employee .构造函数__init__中的**kwargs意味着您不知道(不是不知道,而是有点忽略)或者您不太关心父类Employee中的参数。 So, how python knows which keywords to accept.那么,python 如何知道要接受哪些关键字。 The answer here super().__init__(**kwargs) this **kwargs specifically means whatever keyword in the Employee class will be accepted.这里的答案super().__init__(**kwargs) this **kwargs具体意味着Employee类中的任何关键字都将被接受。 Here's the logic, the kwargs in __init__() must be there which is actually in the parent class, therefore, kwargs must be is the super().__init__() as well.这是逻辑, __init__()中的kwargs必须存在,实际上在父类中,因此, kwargs也必须是super().__init__()

Now, let us move to this line of code:现在,让我们转到这行代码:

class Hourly(Employee):

    def __init__(self, rate=''):
        self.rate = rate
        super().__init__(**kwargs)

the __init__ does not contain **kwargs , so how can super().__init__ know what parameters to inherit. __init__不包含**kwargs ,所以super().__init__怎么知道要继承哪些参数。 We need to fix that by adding **kwargs , so the code will be like this:我们需要通过添加**kwargs来解决这个问题,所以代码将是这样的:

class Hourly(Employee):

    def __init__(self, rate='', **kwargs):
        self.rate = rate
        super().__init__(**kwargs)

Let us turn to the arguments now: How many args are there in the __init__() method?现在让我们转向论点: __init__()方法中有多少个args

--> We have two positional arguments ( self the instance itself. the second arg is rate which it takes a default value. --> And the third arg is kwargs , so you must pass a dictionary here. That is the reason I started saying kwargs is a dict : --> 我们有两个位置参数( self实例本身。第二个参数是rate ,它采用默认值。--> 第三个argkwargs ,所以你必须在这里传递一个字典。这就是我开始的原因说kwargs是一个dict

Let us analyze the error:让我们分析一下错误:

 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

The constructor __init__() takes one or maximum two (why 1 or two, because one argument has a default value ( rate = ' ' ) Not reasonable default arg by the way. But we provided five.构造函数__init__()接受一个或最多两个(为什么是 1 或两个,因为一个参数有一个默认值( rate = ' ' ) 顺便说一下,这是不合理的默认arg 。但我们提供了五个。

So, Let us find a way to add only one or two (the instance itself, or the instance plus rate when you want to overwrite the default value) and adding the key-word arg .所以,让我们想办法只添加一个或两个(实例本身,或者当您想要覆盖默认值时实例加上速率)并添加key-word arg We can do that in two ways:我们可以通过两种方式做到这一点:

first : pass a dict when instantiating, but you have to put ** at the beginning: Be sure to pass the keys exactly as written in the parent class. first : 实例化时传递一个dict ,但你必须把**放在开头:确保传递的键与父类中写的完全一样。

bob = Hourly(rate='12%', **{'firstname':'Bar', 
                                            'lastname':'Bar', 'age':23, 'dob': '12/1/2019'})

Second (the neat one) : define a dict_variable then pass it when instantiating with ** upfront.第二个(整洁的) :定义一个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)

Finally: The __str__() has sex attribute which does not exist any class.最后: __str__()具有不存在任何类的性别属性。 It is either to add that in the parent class.要么在父类中添加它。 I think this is obvious, and I am not going to talk about it.我认为这很明显,我不打算谈论它。 (Note, I excluded the __str__() from the full code, but you should do it in yours) (注意,我从完整代码中排除了__str__() ,但你应该在你的代码中这样做)

Here is the full code:这是完整的代码:

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

Here is the result这是结果

The hourly rate of Bob is: 12% 

You can check the attributes as well this way:您也可以通过以下方式检查属性:

for key, val in bob.__dict__.items():
    print(f'{key}: {val}')

to get the results得到结果

rate: 12%
firstname: Bob
lastname: Bar
age: 23
dob: 12/1/2019

This should work这应该工作

class Employee:
    def __init__(self, firstname='', lastname='', age='', dob=''):
        self.firstname = firstname
        self.lastname = lastname
        self.age = age 
        self.dob = dob 

Then you have child class然后你有子班

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

Now,let us make instance现在,让我们做一个实例

bob = Hourly2('Bob', 'Bar', '23', '12/1/2019')

We can check attributes我们可以检查属性

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']

Finally最后

bob.age
'23'

And

bob.get_rate('$12')
My hourly rate is $12

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 多重继承 Python,super().__init__ 问题 - Multiple inheritance Python, super().__init__ issue Python 多个 Inheritance super().__init__() - Python Multiple Inheritance super().__init__() 多重继承中 super().__new__ 和 super().__init__ 的混淆动作 - The confusing action of super().__new__ and super().__init__ in multiple inheritance tkinter,在 super().__init__ 上传递 *args *kwargs - tkinter, passing *args *kwargs on super().__init__ 这在 python super().__init__(**kwargs) 中有什么作用 - What does this to in python super().__init__(**kwargs) 超级构造函数对__init__使用* args和** kwargs - super constructor use *args and **kwargs for __init__ 直接将** kwargs传递给super()的关键字。 - Keyword for direct passthrough of **kwargs to super().__init__ 为什么在多次继承中执行Base .__ init __(self)而不是super().__ init __()时会跳过__init__? - Why is an __init__ skipped when doing Base.__init__(self) in multiple inheritance instead of super().__init__()? 多继承如何与super()和不同的__init __()参数一起使用? - How does multiple inheritance work with the super() and different __init__() arguments? 在 fractions.Fraction 子类中调用 super().__init__(*args, **kwargs) 只接受要初始化的实例 - Calling super().__init__(*args, **kwargs) in a fractions.Fraction subclass accepts only the instance to initialize
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM