Вопрос

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

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

и:

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

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

Это было полезно?

Решение

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

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

Другие советы

В чем разница?

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 и правильные аргументы метода right могут быть сложными.Если вы знаете, что используете super правильно с одиночным наследованием, что делает отладку менее сложной в дальнейшем.

Внедрение зависимостей

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

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

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

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

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

    A
   / \
  B   C
   \ /
    D

В данном случае МРО of 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 с super (большая иерархия)
  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__() является статическим методом, 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 с помощью метода класса

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

super(B, cls_or_subcls)

3- Призыв super(B, cls) разрешается в рамках A и применяет его к cls.С тех пор как alternate_constructor() является ли метод класса вызовом super(B, cls).alternate_constructor(...) будет неявно передавать ссылку на cls в качестве первого аргумента к Aверсия 's для 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)
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. метод SomeBaseClass.Другими словами, если это super(SomeBaseClass,self), прокси - объект , который super возвраты были бы object

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

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

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

вот только решение для этого сценария:

  1. классы верхнего уровня в вашей иерархии должны наследоваться от пользовательского класса, такого как SuperObject:
  2. если классы могут принимать разные аргументы, всегда передавайте все полученные вами аргументы суперфункции в качестве аргументов ключевого слова и всегда принимайте **kwargs.
class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

пример использования:

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

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

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

выходной сигнал:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

Много отличных ответов, но для тех, кто изучает визуализацию:Сначала давайте рассмотрим с аргументами super, а затем без.super inheritance tree example

Представьте, что есть пример jack созданный на основе класса Jack, у которого есть цепочка наследования, как показано зеленым цветом на рисунке.Зовущий:

super(Jack, jack).method(...)

будет использовать MRO (Порядок разрешения метода) из jack (его дерево наследования в определенном порядке), и начнет поиск с Jack.Почему можно предоставить родительский класс?Что ж, если мы начнем поиск с экземпляра jack, он нашел бы метод экземпляра, весь смысл в том, чтобы найти его родительский метод.

Если кто-то не предоставляет аргументы super, это похоже на то, что первый аргумент, переданный в, является классом self, и второй аргумент , переданный в , является self.Они автоматически вычисляются для вас в Python3.

Однако скажем, что мы не хотим использовать Jackметод, вместо того чтобы передавать в Jack, мы могли бы пройти в Jen чтобы начать поиск метода вверх по Jen.

Он выполняет поиск по одному слою за раз (ширина не глубина), напримересли Adam и Sue оба имеют требуемый метод, тот, что из Sue будет найден первым.

Если Cain и Sue у обоих был требуемый метод, Cainметод s был бы вызван первым.Это соответствует в коде:

Class Jen(Cain, Sue):

MRO выполняется слева направо.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top