Что делает "супер" в Python?

в чем разница между:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

и:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

Я видел super используется довольно много в классах только с одним наследованием. Я понимаю, почему вы используете его в множественном наследовании, но неясно, каковы преимущества его использования в такой ситуации.

6 ответов


преимущества super() в одиночном наследовании минимальны - в основном, вам не нужно жестко кодировать имя базового класса в каждый метод, который использует его родительские методы.

однако практически невозможно использовать множественное наследование без super(). Это включает в себя общие идиомы, такие как mixins, интерфейсы, абстрактные классы и т. д. Это распространяется на код, который позже расширяет ваш. Если кто-то позже хотел написать класс, который продлен Child и mixin, их код будет не работает должным образом.


какая разница?

SomeBaseClass.__init__(self) 

означает вызов SomeBaseClass ' s __init__. в то время как

super(Child, self).__init__()

означает вызвать bound __init__ из родительского класса, который следует за Child в порядке разрешения метода экземпляра (MRO).

если экземпляр является подклассом дочернего, может быть другой родитель, который следующий в MRO.

объясняется просто

когда вы пишете класс, вы хотите, чтобы другие классы могли использовать он. super() облегчает другим классам использование класса, который вы пишете.

как говорит Боб Мартин, хорошая архитектура позволяет откладывать принятие решений как можно дольше.

super() может включить такую архитектуру.

когда другой класс подклассов класса, который вы написали, он также может наследовать от других классов. И у этих классов может быть __init__ это происходит после этого __init__ на основе порядка классов для метод разрешения.

без super вы, скорее всего, жестко закодируете родителя класса, который вы пишете (как в Примере). Это будет означать, что вы не будете называть следующий __init__ в MRO, и вы, таким образом, не сможете повторно использовать код в нем.

если вы пишете свой собственный код для личного использования, вы можете не заботиться об этом различии. Но если вы хотите, чтобы другие использовали ваш код, используя super одна вещь которая позволяет большей гибкости для потребителей код.

Python 2 Против 3

это работает в Python 2 и 3:

super(Child, self).__init__()

это работает только в Python 3:

super().__init__()

он работает без аргументов, перемещаясь в кадре стека и получая первый аргумент метода (обычно self для метода экземпляра или cls для метода класса, но могут быть и другие имена) и нахождение класса (например,Child) в свободных переменных (он просматривается с именем __class__ бесплатно переменная замыкания в методе).

я предпочитаю демонстрировать кросс-совместимый способ использования super, но если вы используете только Python 3, Вы можете вызвать его без аргументов.

косвенность с прямой совместимостью

что это дает тебе? Для единичного наследования примеры из вопроса практически идентичны с точки зрения статического анализа. Однако, используя super дает слой с вперед совместимость.

Forward совместимость очень важна для опытных разработчиков. Вы хотите, чтобы ваш код продолжал работать с минимальными изменениями по мере его изменения. Когда вы смотрите на свою историю изменений, вы хотите точно увидеть, что изменилось, когда.

вы можете начать с одного наследования, но если вы решите добавить другой базовый класс, вам нужно только изменить строку с основаниями - если базы изменятся в классе, который вы наследуете (скажем, добавлен mixin), вы измените ничего в этом классе. В частности, в Python 2, Получение аргументов super и правильные аргументы метода могут быть трудными. Если вы знаете, что используете super правильно с одним наследованием, что делает отладку менее сложной в будущем.

Инъекции Зависимостей

другие люди могут использовать ваш код и ввести родителей в разрешение метода:

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')

class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)

class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super(SuperChild, self).__init__()

скажем, вы добавляете другой класс к своему объекту и хотите ввести класс между Foo и Bar (для тестирования или по какой-либо другой причине):

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super(InjectMe, self).__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

использование un-super child не удается ввести зависимость, потому что ребенок, который вы используете, жестко закодировал метод, который будет вызываться после его собственного:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called
класс с ребенком, который использует super может правильно ввести зависимость:
>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

устранение комментарий

почему в мире это было бы полезно?

Python линеаризирует сложное дерево наследования через алгоритм линеаризации C3 для создания порядка разрешения метода (MRO).

мы хотим методов посмотрела в таком порядке.

для метода, определенного в родителе, чтобы найти следующий в этом порядке без super, что бы

  1. получить mro от типа экземпляра
  2. ищите тип, который определяет метод
  3. найти следующий тип с помощью метода
  4. свяжите этот метод и вызовите его с ожидаемыми аргументами

на UnsuperChild не должно иметь доступа к InjectMe. Почему не вывод " всегда избегайте использования super"? Я что-то упускаю?

на UnsuperChild тут не иметь доступ к InjectMe. Это UnsuperInjector, который имеет доступ к InjectMe - и все же не могу назвать, что метод класса из метода, который он наследует от UnsuperChild.

оба дочерних класса намерены вызвать метод с тем же именем, что и следующий в MRO, который может быть другое класс он не знал, когда он был создан.

без super hard-codes его родительский метод-таким образом, это ограничило поведение его метода, и подклассы не могут вводить функциональность в цепочку вызовов.

один с super имеет большую гибкость. Цепочка вызовов для методов может быть перехвачена и введена функциональность.

вам может не понадобиться эта функциональность, но подклассы вашего кода могут.

вывод

всегда использовать super для ссылки на родительский класс, а не жесткого кодирования его.

вы собираетесь ссылаться на родительский класс, который является следующим в строке, а не конкретно на тот, который вы видите, наследует ребенок от.

не используя super может накладывать ненужные ограничения на пользователей вашего кода.


разве все это не предполагает, что базовый класс является классом нового стиля?

class A:
    def __init__(self):
        print("A.__init__()")

class B(A):
    def __init__(self):
        print("B.__init__()")
        super(B, self).__init__()

не будет работать в Python 2. class A должен быть новый стиль, i.e:class A(object)


Я немного поиграл с super(), и признал, что мы можем изменить порядок вызова.

например, у нас есть следующая иерархическая структура:

    A
   / \
  B   C
   \ /
    D

в этом случае MRO D будет (только для Python 3):

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

давайте создадим класс, в котором super() вызовы после выполнения метода.

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:   
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:  
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / ⇖
  B ⇒ C
   ⇖ /
    D

таким образом, мы можем видеть, что порядок разрешения такой же, как в MRO. Но когда мы зовем super() в начало метод:

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:   
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:  
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...: 
I'm from A
I'm from C
I'm from B
I'm from D

у нас есть другой порядок, он отменен порядком кортежа MRO.

    A
   / ⇘
  B ⇐ C
   ⇘ /
    D 

для дополнительного чтения я бы рекомендовал следующие ответы:

  1. пример линеаризации C3 с супер (большая иерархия)
  2. важные изменения поведения между старыми и новыми классами стилей
  3. внутренняя история о классах нового стиля

при вызове super() чтобы разрешить родительскую версию classmethod, метода экземпляра или staticmethod, мы хотим передать текущий класс, область которого мы находимся в качестве первого аргумента, чтобы указать, к какой области родителя мы пытаемся разрешить, и в качестве второго аргумента объект интереса, чтобы указать, к какому объекту мы пытаемся применить эту область.

рассмотрим иерархию классов A, B и C где каждый класс является родителем одного следуя за ним, и a, b и c соответствующие экземпляры каждого.

super(B, b) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c) 
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

используя super С staticmethod

например,super() внутри __new__() метод

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

объяснение:

1-хотя это обычно для __new__() взять в качестве первого параметра ссылку на класс, называя это не реализовано в Python как classmethod, но скорее staticmethod. То есть ссылка на класс должна быть передана явно в качестве первого аргумента при вызове __new__() напрямую:

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2 - при вызове super() чтобы добраться до родительского класса мы передаем ребенка класс A в качестве первого аргумента мы передаем ссылку на объект интереса, в этом случае это ссылка на класс, которая была передана, когда A.__new__(cls) называлась. В большинстве случаев это также является ссылкой на дочерний класс. В некоторых ситуациях это может быть, например, в случай наследования нескольких поколений.

super(A, cls)

3-так как, как общее правило __new__() - это staticmethod, super(A, cls).__new__ также вернет staticmethod и должен быть предоставлен все аргументы явно, включая ссылку на объект insterest, в этом случае cls.

super(A, cls).__new__(cls, *a, **kw)

4 - делать то же самое без super

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

используя super С методом экземпляра

например,super() изнутри __init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

объяснение:

1- __init__ - это метод экземпляра, что означает, что он принимает в качестве первого аргумента ссылку на экземпляр. При вызове непосредственно из экземпляра ссылка передается неявно, то есть вам не нужно указывать ее:

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2 - при вызове super() внутри __init__() мы передаем дочерний класс как первый аргумент и объект интереса как второй аргумент, который в целом является ссылкой на экземпляр дочернего класса.

super(A, self)

3 - вызов super(A, self) возвращает прокси-сервер, который разрешит область и применит ее к self как будто это теперь экземпляр родительского класса. Назовем это прокси s. С __init__() является методом экземпляра вызов s.__init__(...) неявно передаст ссылку self в качестве первого аргумента для родителей __init__().

4 - сделать то же самое без super нужно передать ссылку на экземпляр, явно родительская версия __init__().

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

используя super С classmethod

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

объяснение:

1 - classmethod может быть вызван из класса напрямую и принимает в качестве первого параметра ссылку на класс.

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2 - при вызове super() в classmethod для разрешения его родительской версии мы хотим передать текущий дочерний класс в качестве первого аргумента, чтобы указать, какую родительскую область мы пытаемся чтобы решить, и объект интереса в качестве второго аргумента, чтобы указать, к какому объекту мы хотим применить эту область, которая в целом является ссылкой на сам дочерний класс или один из его подклассов.

super(B, cls_or_subcls)

3 - вызов super(B, cls) разрешает область A и применяет его к cls. С alternate_constructor() является classmethod вызов super(B, cls).alternate_constructor(...) будет неявно передавать ссылку cls в качестве первого аргумента Aверсия alternate_constructor()

super(B, cls).alternate_constructor()

4 - сделать то же самое без использования super() вам нужно будет получить ссылку на unbound версия A.alternate_constructor() (т. е. явная версия функции). Просто сделать это не получится:

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

выше не будет работать, потому что A.alternate_constructor() метод принимает неявную ссылку на A в качестве первого аргумента. The cls передается здесь будет второй аргумент.

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)

class Child(SomeBaseClass): def __init__(self): SomeBaseClass.__init__(self) Это довольно легко понять.

class Child(SomeBaseClass): def __init__(self): super(Child, self).__init__()

хорошо, что произойдет, если вы используете super(Child,self)?

при создании дочернего экземпляра его MRO(порядок разрешения метода) находится в порядке (Дочерний, SomeBaseClass, object) на основе наследования. (предположим, что SomeBaseClass не имеет других родителей, кроме объекта по умолчанию)

, передав Child, self, super поиск в MRO self экземпляр и вернуть прокси-объект рядом с дочерним, в этом случае это SomeBaseClass, этот объект затем вызывает __init__ метод SomeBaseClass. Другими словами, если это super(SomeBaseClass,self) прокси-объект, который super отдача будет object

для multi наследования MRO может содержать много классов, поэтому в основном super позволяет вам решить, где вы хотите начать поиск в MRO.