【问题标题】:correct way to use super (argument passing)使用 super 的正确方法(参数传递)
【发布时间】:2012-01-23 14:04:35
【问题描述】:

所以我关注Python's Super Considered Harmful,去测试他的例子。

但是,Example 1-3 应该显示在处理需要不同参数的 __init__ 方法时调用 super 的正确方法,但完全不起作用。

这是我得到的:

~ $ python example1-3.py 
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
Traceback (most recent call last):
  File "Download/example1-3.py", line 27, in <module>
    E(arg=10)
  File "Download/example1-3.py", line 24, in __init__
    super(E, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 14, in __init__
    super(C, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 4, in __init__
    super(A, self).__init__(*args, **kwargs)
  File "Download/example1-3.py", line 19, in __init__
    super(D, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 9, in __init__
    super(B, self).__init__(*args, **kwargs)
TypeError: object.__init__() takes no parameters

似乎object 本身违反了文档中提到的最佳实践之一,即使用super 的方法必须接受*args**kwargs

现在,显然 Knight 先生希望他的示例能够工作,那么这是否在最新版本的 Python 中有所改变?我检查了 2.6 和 2.7,但都失败了。

那么处理这个问题的正确方法是什么?

【问题讨论】:

  • 我的首选方式是:扁平且简单的继承层次结构。
  • 您还应该阅读"Python's super() considered super" 以获得平衡的观点:)
  • @BjörnPollex:谢谢!您的链接提供了问题的答案:您可以编写一个继承自object 的“根类”,并确保正确调用object__init__
  • 请注意,__init__ on object 会默默地忽略 Python 2.5 上的任何参数。这在 Python 2.6 中发生了变化。
  • @Wilfred:嘿,感谢您回答实际问题!现在我知道为什么这篇文章已经过时了!

标签: python


【解决方案1】:

有时两个类可能有一些共同的参数名称。在这种情况下,您不能从**kwargs 中弹出键值对或从*args 中删除它们。相反,您可以定义一个 Base 类,它与 object 不同,吸收/忽略参数:

class Base(object):
    def __init__(self, *args, **kwargs): pass

class A(Base):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(Base):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(arg, *args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(arg, *args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(arg, *args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10)

产量

MRO: ['E', 'C', 'A', 'D', 'B', 'Base', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B

请注意,要使其正常工作,Base 必须是 MRO 中的倒数第二个类。

【讨论】:

  • 不应该Base打电话super(Base, self).__init__()吗?
  • 为此,Base 必须位于 MRO 的末尾(object 除外)。调用object.__init__ 没有任何作用,所以不调用super(Base, self).__init__() 也没关系。事实上,我认为不包括super(Base, self).__init__() 可能更清楚,因为Base 是行尾。
【解决方案2】:

如果你打算有很多继承(这里就是这种情况)我建议你使用**kwargs传递所有参数,然后在你使用它们之后立即pop它们(除非你需要它们在上类)。

class First(object):
    def __init__(self, *args, **kwargs):
        self.first_arg = kwargs.pop('first_arg')
        super(First, self).__init__(*args, **kwargs)

class Second(First):
    def __init__(self, *args, **kwargs):
        self.second_arg = kwargs.pop('second_arg')
        super(Second, self).__init__(*args, **kwargs)

class Third(Second):
    def __init__(self, *args, **kwargs):
        self.third_arg = kwargs.pop('third_arg')
        super(Third, self).__init__(*args, **kwargs)

这是解决这类问题的最简单方法。

third = Third(first_arg=1, second_arg=2, third_arg=3)

【讨论】:

    【解决方案3】:

    正如Python's super() considered super 中所解释的,一种方法是让类吃掉它需要的参数,然后传递其余的。因此,当调用链到达object 时,所有参数都被吃掉了,object.__init__ 将在没有参数的情况下被调用(正如它所期望的那样)。所以你的代码应该是这样的:

    class A(object):
        def __init__(self, *args, **kwargs):
            print "A"
            super(A, self).__init__(*args, **kwargs)
    
    class B(object):
        def __init__(self, *args, **kwargs):
            print "B"
            super(B, self).__init__(*args, **kwargs)
    
    class C(A):
        def __init__(self, arg, *args, **kwargs):
            print "C","arg=",arg
            super(C, self).__init__(*args, **kwargs)
    
    class D(B):
        def __init__(self, arg, *args, **kwargs):
            print "D", "arg=",arg
            super(D, self).__init__(*args, **kwargs)
    
    class E(C,D):
        def __init__(self, arg, *args, **kwargs):
            print "E", "arg=",arg
            super(E, self).__init__(*args, **kwargs)
    
    print "MRO:", [x.__name__ for x in E.__mro__]
    E(10, 20, 30)
    

    【讨论】:

    • 那么,E(arg = 10) 呢?我不喜欢这种方法,需要提前了解MRO才能使用。我编写从object 继承的“根类”的另一种方法似乎更干净。
    • 如果调用的函数不共享参数名称,此方法很有用。我承认我对super没有太多的实践经验,所以这种方法可能确实比较理论。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-07
    • 2021-07-19
    • 2022-12-03
    • 1970-01-01
    • 2017-07-01
    • 1970-01-01
    相关资源
    最近更新 更多