Что «супер» делает в Python? - PullRequest
489 голосов
/ 21 октября 2008

Какая разница между:

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

и

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

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

Ответы [ 6 ]

281 голосов
/ 21 октября 2008

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

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

261 голосов
/ 02 ноября 2015

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

SomeBaseClass.__init__(self) 

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

super(Child, self).__init__()

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

Если экземпляр является подклассом Child, может быть другой родитель, следующий за 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 дает вам уровень косвенности с прямой совместимостью.

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

Вы можете начать с одного наследования, но если вы решите добавить другой базовый класс, вам нужно будет только изменить строку с базами - если базы меняются в классе, от которого вы наследуете (скажем, добавлен миксин), вы ничего не изменил бы в этом классе. В частности, в 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 не удается внедрить зависимость, поскольку используемый вами дочерний элемент жестко запрограммировал метод, который будет вызываться после его собственного:

>>> 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, жестко кодирует метод своего родителя - поэтому он ограничил поведение своего метода, и подклассы не могут вводить функциональность в цепочку вызовов.

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

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

Заключение * * тысяча сто тридцать-шесть Всегда используйте super для ссылки на родительский класс вместо его жесткого кодирования. Вы намереваетесь сослаться на следующий родительский класс, а не на тот, от которого наследует дочерний класс. Неиспользование super может наложить ненужные ограничения на пользователей вашего кода.

35 голосов
/ 22 октября 2008

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

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 должен быть в новом стиле, то есть: class A(object)

27 голосов
/ 29 декабря 2016

Я немного поиграл с 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. Внутренняя история на уроках нового стиля
16 голосов
/ 07 сентября 2016

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

Рассмотрим иерархию классов 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 со статическим методом

например. используя 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 как метод класса, а скорее как статический метод. То есть ссылка на класс должна быть явно передана в качестве первого аргумента при непосредственном вызове __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__() является статическим методом, super(A, cls).__new__ также будет возвращать статический метод, и ему необходимо явно указать все аргументы, включая ссылку на объект интереса, в данном случае 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 с классом

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- Метод класса может быть вызван напрямую из класса и принимает в качестве первого параметра ссылку на класс.

# 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() в методе класса для разрешения его родительской версии мы хотим передать текущий дочерний класс в качестве первого аргумента, указывающего, в какую область видимости родителя мы пытаемся разрешить, и объект представляет интерес как второй аргумент, указывающий, к какому объекту мы хотим применить эту область, которая в общем случае является ссылкой на сам дочерний класс или один из его подклассов.

super(B, cls_or_subcls)

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

super(B, cls).alternate_constructor()

4 - чтобы сделать то же самое без использования super(), вам потребуется получить ссылку на несвязанную версию 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 в качестве первого аргумента. Передаваемый здесь 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)
0 голосов
/ 17 июля 2018
class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

Это довольно легко понять.

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

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

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

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

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

...