【问题标题】:Calling super().__init__(**kwargs), and multiple inheritance?调用 super().__init__(**kwargs) 和多重继承?
【发布时间】:2018-11-06 02:09:48
【问题描述】:

我正在努力学习和理解如何在 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 的方式有什么不同?

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

https://pastebin.com/NYGJfMik

【问题讨论】:

  • 至于None,那是因为你的get_rate 方法没有返回任何东西。见How is returning the output of a function different from printing it?。至于**kwargs:你放错地方了。它们应该在Hourly.__init__ 方法中使用。 Employee 不应该调用 super().__init__(**kwargs)super().__init__() 或任何类似的东西。
  • Employee中删除它们并添加到Hourlydef __init__(self, rate='', **kwargs):
  • 啊,这是因为您将它们作为位置参数而不是关键字参数传递。您可以将所有**kwargs 更改为*args, **kwargs。但请注意,您将 'Bob' 作为 rate 参数的值传递,所以也许您应该只使用关键字参数来避免犯这样的错误。
  • 啊,我想我明白你在说什么了。书中的类是为多重继承而设计的。 (您可以看到Friend 继承自ContactAddressHolder。)您可能会发现我的回答here 很有用。
  • 我不确定我是否应该这样做。您的代码中有很多单独的小问题,可能有点过于宽泛,不适合 StackOverflow。

标签: python python-3.x multiple-inheritance super


【解决方案1】:

你在这里遇到的问题并不是超级特定的,而是更特定于 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)

如果我们遵循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)。 Mixins 甚至可能需要super().__init__() 调用,尽管它们甚至不从任何类继承。总而言之,如果您使用多重继承,您的代码会变得非常复杂,因此如果您真的不需要它,通常最好考虑其他方法来为您的问题建模。

【讨论】:

  • 好答案。不过,它可能更适合this question。考虑到这个问题有多混乱,我认为它在这里不会有多大好处。请考虑将您的答案移至我链接的问题;它会被更多的人看到(你只需要做一些小的改变)。
  • 我对这个问题的一个抱怨与你的这句话有关:“如果你只继承一个类 super,你就不需要 super”。这绝对是正确的,但使用super 仍然是一个好习惯,只是因为它可以让您避免对父类的名称进行硬编码。我担心你表达这句话的方式可能会阻止人们无缘无故地使用super
  • @Aran-Fey 谢谢。对我来说,理解您链接的线程中的问题已经需要对super 有基本的了解,我试图在我的回答中传达这一点。尽管the linked blog entry 可能是迄今为止我发现的super 上最好的资源,但我并没有真正找到一个要求我提供基本知识的SO 问题。我稍微更改了措辞以纳入您的投诉,但我个人更愿意避免 super 在不需要的情况下。
  • 谢谢,很好的答案,我肯定需要阅读/练习更多
  • @goblin_rocket 如果您还没有看到它,this blog article 也链接到了 Aran-Fey 链接到的答案中真的很好。唯一缺少的是 mixins,虽然它没有超类,但你需要调用 super。
【解决方案2】:

这应该可以工作

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

那么你就有了子类

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('$12')
My hourly rate is $12

【讨论】:

  • 那个子类完全没有意义。为什么要有一个接受参数并返回它的方法?如果您要将该参数分配给属性,为什么将其称为“get_rate”?无论如何,为什么不将rate 作为构造函数参数?
  • 是的,但它所做的事情与你的完全不同。
  • 我已经进行了编辑,您能否提供有关构造函数参数的示例或链接。
  • 这里没有说明如何使用 warts 和 super,如果我不想在 super 的 __init__() 中明确指定参数怎么办?
猜你喜欢
  • 2015-05-04
  • 2020-07-05
  • 2017-07-30
  • 2021-11-23
  • 2021-11-28
  • 1970-01-01
  • 1970-01-01
  • 2016-02-25
  • 1970-01-01
相关资源
最近更新 更多