【问题标题】:python super calling child methodspython超级调用子方法
【发布时间】:2012-09-10 18:52:36
【问题描述】:

关于super() 的用法有很多问题,但似乎都没有回答我的问题。

当从子类调用super().__init__() 时,超构造函数中的所有方法调用实际上都取自子类。考虑以下类结构:

class A(object):
    def __init__(self):
        print("initializing A")
        self.a()
    def a(self):
        print("A.a()")

class B(A):
    def __init__(self):
        super().__init__()
        # add stuff for B
        self.bnum=3 # required by B.a()        
    def a(self):
        print("B.a(), bnum=%i"%self.bnum)

b=B()

失败了

initializing A
Traceback (most recent call last):
  File "classmagic.py", line 17, in 
    b=B()
  File "classmagic.py", line 11, in __init__
    super().__init__()
  File "classmagic.py", line 5, in __init__
    self.a()
  File "classmagic.py", line 15, in a
    print("B.a(), bnum=%i"%self.bnum)
AttributeError: 'B' object has no attribute 'bnum'

这里我调用B()中的超级构造函数来初始化一些基本结构(其中一些作为自己的函数a()执行)。但是,如果我也重写 a() 函数,则在调用 A 的构造函数时会使用此实现,但由于 AB 一无所知,并且可能使用不同的内部变量,因此会失败。

这可能很直观,也可能不直观,但是当我希望 A 中的所有方法都只能访问那里实现的功能时,我该怎么办?

【问题讨论】:

    标签: python oop superclass


    【解决方案1】:

    如果我们通过B的实例化步骤:

    • 我们调用super(B,self).__init__,即A.__init__
    • 调用self.a(),在我们的例子中就是B.a()
    • 使用self.bnum

    除了 bnum 还没有定义...所以,AttributeError

    对于这种特殊情况,在B.__init__ 中定义您的bnum 就足够了调用super(B,self).__init__

    class B(A):
        def __init__(self):
           self.bnum=3
           super(B, self).__init__()
    

    在进行名称修改的黑暗、黑暗路径之前,您可能只想花时间组织子类的代码:是否应该在父类之前执行 以初始化一些特殊变量, 或 父级之后,替换一些默认值?

    【讨论】:

    • 我可以看到。不过,我真正的问题是涉及更多一点(子类中的实现使用了一个未在超类​​命名空间中导入的模块)。
    • 我认为他不希望 B.a 在 super 调用期间被调用,因此在调用 super 之前声明 bnum 时不会再抛出 AttributeError ,实际上它不是解决他的问题。
    • 啊。那么,需要super(B,self).__init__ 吗?
    【解决方案2】:

    考虑这个调用:

    class B(A):
        def __init__(self):
            A.__init__(self)
    

    当您致电super().__init__() 时会发生这种情况。这反过来又调用self.a(),这当然是B 类的函数a 而不是A,因为self 属于B 类。正如 Martijn 所说,您可以使用双下划线名称,或显式使用类名,否则无法从超类调用覆盖的方法。

    【讨论】:

      【解决方案3】:

      您需要使用A.a(self),而不是调用self.a()。但这对于特定类上的所有方法并不常见,您应该重新评估B 是否应该从A 继承

      【讨论】:

      • 这需要更改 A 的实现,我想避免这种情况,但感谢您指出这一点
      • @thias -- 在我看来,无论如何,您都需要更改 A 的实现(无论是按照 Martijn 的建议进行名称修改,还是通过明确指定您使用的方法)打电话)
      • 这是真的。我现在通过在构造函数中调用__a 函数并实现一个简单调用__a 并且可以被覆盖的a() 函数来实现。这样,我有一个一致的外部接口,但仍然可以从A.__init__ 调用__a
      【解决方案4】:

      如果您的代码必须调用无法覆盖的特定私有方法,请使用以下划线开头的名称:

      class A(object):
          def __init__(self):
              print("initializing A")
              self.__a()
          def __a(self):
              print("A.a()")
      
      class B(A):
          def __init__(self):
              super().__init__()
              # add stuff for B
              self.bnum=3 # required by B.a()        
          def __a(self):
              print("B.__a(), bnum=%i"%self.bnum)
      

      Python "mangles" 这样的方法名称通过添加类名(加上下划线)来最小化子类用自己的版本覆盖它们的机会。

      PEP 8 Python Style Guide 对私有名称修改有这样的看法:

      如果你的类打算被子类化,并且你有属性 您不想让子类使用,请考虑将它们命名为 双前导下划线,没有尾随下划线。这调用 Python 的名称修饰算法,其中类的名称为 融入属性名称。这有助于避免属性名称 碰撞应该子类不经意地包含与 同名。

      注意1:注意在mangled中只使用了简单的类名 名称,因此如果子类选择相同的类名和属性 名字,你仍然可以得到名字冲突。

      注意 2:名称修饰可以有某些用途,例如调试和 __getattr__(),不太方便。但是名称修饰算法 有据可查且易于手动执行。

      注 3:不是每个人都喜欢名字修饰。尝试平衡需要 避免与高级调用者可能使用的意外名称冲突。

      【讨论】:

      • 感谢__mangling,我认为前导下划线只是惯例
      • @thias: 如果前面有 双下划线,则不会发生修改;带有前导和尾随下划线的方法是“魔术”方法、钩子等,主要是为 python 本身保留的。想想__init____getattr__ 等。只是为了明确一点。 :-)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-08
      • 1970-01-01
      相关资源
      最近更新 更多