通过。了解Python super()

这个问题在这里已经有了答案:

  • “超级”在Python中做什么? 5个答案

  • super()可以避免显式引用基类,这很好。 但主要优点是多重继承,可以发生各种有趣的事情。 如果你还没有,请参阅super的标准文档。

    请注意,在Python 3.0中更改了语法:您可以只说super().__init__()而不是super(ChildB, self).__init__()哪个IMO更好。


    我想了解super()

    我们使用super的原因是,可能使用协作多重继承的子类将在方法解析顺序(MRO)中调用正确的下一个父类函数。

    在Python 3中,我们可以这样调用它:

    class ChildB(Base):
        def __init__(self):
            super().__init__() 
    

    在Python 2中,我们需要像这样使用它:

            super(ChildB, self).__init__()
    

    没有超级,你在使用多重继承方面的能力有限:

            Base.__init__(self) # Avoid this.
    

    我在下面进一步解释。

    “这段代码实际上有什么不同?”:

    class ChildA(Base):
        def __init__(self):
            Base.__init__(self)
    
    class ChildB(Base):
        def __init__(self):
            super(ChildB, self).__init__()
            # super().__init__() # you can call super like this in Python 3!
    

    这段代码的主要区别在于,您在__init__使用super获得了一个间接层,它使用当前类来确定下一个类的__init__在MRO中查找。

    我在标准问题(如何在Python中使用'super')中说明了这种差异,它演示了依赖注入协作多重继承

    如果Python没有super

    这里的代码实际上与super非常相似(它是如何在C中实现的,减去一些检查和回退行为并转换为Python):

    class ChildB(Base):
        def __init__(self):
            mro = type(self).mro()             # Get the Method Resolution Order.
            check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
            while check_next < len(mro):
                next_class = mro[check_next]
                if '__init__' in next_class.__dict__:
                    next_class.__init__(self)
                    break
                check_next += 1
    

    写得更像本地Python:

    class ChildB(Base):
        def __init__(self):
            mro = type(self).mro()
            for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
                if hasattr(next_class, '__init__'):
                    next_class.__init__(self)
                    break
    

    如果我们没有super对象,我们必须在任何地方编写这个手册代码(或重新创建它!),以确保我们在方法解析顺序中调用适当的下一个方法!

    super如何在Python 3中执行此操作,而无需明确告知它从哪个方法调用哪个类和实例?

    它获得调用堆栈框架,并找到该类(隐式存储为本地自由变量, __class__ ,使调用函数成为类的闭包)以及该函数的第一个参数,该参数应该是通知它的实例或类使用哪种方法解析顺序(MRO)。

    由于它需要MRO的第一个参数,所以使用super静态方法是不可能的。

    批评其他答案:

    super()可以避免显式引用基类,这很好。 。 但主要优点是多重继承,可以发生各种有趣的事情。 如果你还没有,请参阅super的标准文档。

    这是相当手动的,并没有告诉我们很多,但super目的不是要避免编写父类。 重点是确保调用方法解析顺序(MRO)中的下一个方法。 这在多重继承中变得重要。

    我会在这里解释。

    class Base(object):
        def __init__(self):
            print("Base init'ed")
    
    class ChildA(Base):
        def __init__(self):
            print("ChildA init'ed")
            Base.__init__(self)
    
    class ChildB(Base):
        def __init__(self):
            print("ChildB init'ed")
            super(ChildB, self).__init__()
    

    让我们创建一个我们希望在Child之后调用的依赖项:

    class UserDependency(Base):
        def __init__(self):
            print("UserDependency init'ed")
            super(UserDependency, self).__init__()
    

    现在请记住, ChildB使用超级, ChildA不会:

    class UserA(ChildA, UserDependency):
        def __init__(self):
            print("UserA init'ed")
            super(UserA, self).__init__()
    
    class UserB(ChildB, UserDependency):
        def __init__(self):
            print("UserB init'ed")
            super(UserB, self).__init__()
    

    并且UserA不调用UserDependency方法:

    >>> UserA()
    UserA init'ed
    ChildA init'ed
    Base init'ed
    <__main__.UserA object at 0x0000000003403BA8>
    

    UserB ,因为ChildB使用super ,所以ChildB

    >>> UserB()
    UserB init'ed
    ChildB init'ed
    UserDependency init'ed
    Base init'ed
    <__main__.UserB object at 0x0000000003403438>
    

    批评另一个答案

    在任何情况下,你都不应该做下面的事情,另一个答案暗示,因为当你子类化ChildB时你肯定会得到错误:

            super(self.__class__, self).__init__() # Don't do this. Ever.
    

    (这个答案不是很聪明或者特别有趣,但是尽管在评论中有直接的批评,并且有超过17个投票结果,但回答者仍坚持建议,直到一位善良的编辑修正了他的问题。)

    说明:这个答案建议调用超级像这样:

    super(self.__class__, self).__init__()
    

    这是完全错误的。 super允许我们查看MRO中的下一个父级(请参阅此答案的第一部分)以了解子级。 如果你告诉super我们在子实例的方法中,它会查找下一个方法(可能是这个),导致递归,可能会导致逻辑失败(在回答者的例子中,它会)或RuntimeError递归深度被超过。

    >>> class Polygon(object):
    ...     def __init__(self, id):
    ...         self.id = id
    ...
    >>> class Rectangle(Polygon):
    ...     def __init__(self, id, width, height):
    ...         super(self.__class__, self).__init__(id)
    ...         self.shape = (width, height)
    ...
    >>> class Square(Rectangle):
    ...     pass
    ...
    >>> Square('a', 10, 10)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 3, in __init__
    TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'
    

    有人指出,在Python 3.0以上,你可以使用

    super().__init__()

    使你的调用,这是简洁的,并不需要你明确引用父OR类名,这可以很方便。 我只是想补充一点,对于Python 2.7或更低版​​本,可以通过编写self.__class__而不是类名来获得这种对名称不敏感的行为,即

    super(self.__class__, self).__init__()
    

    然而,这将打破super的任何从你的类继承的类,其中self.__class__可以返回一个子类。 例如:

    class Polygon(object):
        def __init__(self, id):
            self.id = id
    
    class Rectangle(Polygon):
        def __init__(self, id, width, height):
            super(self.__class__, self).__init__(id)
            self.shape = (width, height)
    
    class Square(Rectangle):
        pass
    

    这里我有一个类Square ,它是Rectangle一个子类。 假设我不想为Square编写单独的构造函数,因为Rectangle的构造函数足够好,但出于某种原因我想实现Square,所以我可以重新实现其他方法。

    当我创建了一个SquaremSquare = Square('a', 10,10) ,Python的要求构造Rectangle ,因为我还没有给Square自己的构造。 但是,在Rectangle的构造函数中, super(self.__class__,self)将返回mSquare的超类,因此它再次调用Rectangle的构造函数。 正如@S_C所提到的,这就是无限循环的发生。 在这种情况下,当我运行super(...).__init__()我调用Rectangle的构造函数,但是因为我没有给它任何参数,所以我会得到一个错误。

    链接地址: http://www.djcxy.com/p/783.html

    上一篇: Understanding Python super() with

    下一篇: How do I make Git ignore file mode (chmod) changes?