Що робить "супер" у Python?


564

Яка різниця між:

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

і:

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

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

Відповіді:


309

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

Однак без багатокористування наслідувати практично неможливо super(). Сюди входять загальні ідіоми, такі як міксин, інтерфейси, абстрактні класи тощо. Це поширюється на код, який згодом розширює ваш. Якби хтось пізніше захотів написати клас, який розширив Childі змішав, його код не працював би належним чином.


6
чи можете ви навести приклад того, що ви маєте на увазі, що з "це не працюватиме належним чином"?
Чарлі Паркер

319

Яка різниця?

SomeBaseClass.__init__(self) 

засіб для виклику SomeBaseClass«S __init__. поки

super(Child, self).__init__()

означає викликати зв'язане __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 дає вам шар непрямості з прямої сумісністю.

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

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


В C, DI такий . код тут . Якщо я додаю ще одну реалізацію listінтерфейсу, скажімо, doublylinkedlistпрограма додає його плавно. Я можу зробити свій приклад більш налаштованим, вводячи config.txtта реалізуючи посилання під час завантаження. Це правильний приклад? Якщо так, то як я пов’язую ваш код? Дивіться першу рекламу DI у вікі. Де можна налаштувати будь-яку нову реалізацію? у вашому коді
переобмін

Нова реалізація створюється шляхом успадкування, наприклад, коли один із класів "Інжектор" успадковує від InjectMeкласу. Однак коментарі не для обговорення, тому я пропоную вам обговорити це далі з іншими у чаті або задати нове запитання на головному сайті.
Аарон Холл

чудова відповідь! але при використанні багаторазового успадкування виникають ускладнення із супер () та __init__функціями. особливо якщо підпис __init__відрізняється між класами в ієрархії. Я додав відповідь, яка зосереджена на цьому аспекті
Авіад Розенхек,

35

Я трохи пограв 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. Історія всередині класів нового стилю

Я не розумію, чому порядок змінюється. У першій частині я розумію, що DBCA, оскільки D - це перший клас, тоді, коли завантажувати self (B, C), врешті-решт буде надруковано B, C, то лише A, оскільки B (A), C (A) вказує на себе для остаточного частина. Якщо я дотримуюся цього розуміння, то чи не повинна друга частина бути схожою на BCAD? Скажіть, будь ласка, трохи поясніть мені.
JJson

Мій поганий, я не помітив, що кожен екземпляр кожного класу був ініційований спочатку супер (). Тоді якщо це так, чи не повинен це бути ABCD? Я якось розумію, як ACBD прийшов, але все ще не міг переконати і все ще трохи заплутався. моє розуміння полягає в тому, що d = D () називається клас D (B, C) з двома самопараметрами, оскільки спочатку ініціюється super (), тоді B викликається разом з його атрибутами, то D не друкується раніше, ніж C, тому що клас D (B, C) містить 2 самопараметри, тому він повинен виконувати другий, який є класом C (A), після виконання не буде більше виконаних самостійних параметрів
JJson

Це відбувається на основі визначення mro .
СХалімон

1
тоді він надрукує C, потім надрукує B і, нарешті, надрукує D. Чи правильно я?
JJson

2
Дуже легко зрозуміти другий, доки ти отримаєш перший. Це просто як стек. Ви натискаєте друк '' в стек і робите super (), коли це зроблено з A, він починає друкувати речі в цій стеці, тому порядок зворотний.
грант

35

Чи не все це припускає, що базовий клас - це клас нового стилю?

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)


20

При дзвінку super() вирішити батьківську версію класового методу, методу екземпляра або 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 зі статичним методом

наприклад , з використанням 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- Метод class може бути викликаний з класу безпосередньо і приймає за свій перший параметр посилання на клас.

# 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()є методом class, виклик super(B, cls).alternate_constructor(...)неявно передасть посилання clsяк перший аргумент на Aверсію 'salternate_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)

6

Безліч чудових відповідей, але для візуальних учнів: Спочатку давайте досліджуємо аргументи до супер, а потім і без. приклад супер спадкового дерева

Уявіть, що це екземпляр, jackстворений з класу Jack, який має ланцюг успадкування, як показано зеленим кольором на малюнку. Дзвінки:

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

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

Якщо не наводити аргументи до супер, це як перший аргумент, переданий у класі self , а другий аргумент передається - це self. Вони автоматично розраховані для вас у Python3.

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

Він здійснює пошук по одному шару (ширина, а не глибина), наприклад, якщо AdamіSue обох є необхідний метод, той зSue них буде знайдений першим.

Якби Cainі Sueобидва мали необхідний метод, Cainметод спочатку буде викликаний. Це відповідає в коді:

Class Jen(Cain, Sue):

MRO - зліва направо.


2

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

щоб відповісти на цю частину та вміти ефективно використовувати, 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

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

Це досить легко зрозуміти.

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

Гаразд, що станеться зараз, якщо ви користуєтесь super(Child,self)?

Коли створений доменний екземпляр, його MRO (Order Resolution Order) знаходиться в порядку (Child, SomeBaseClass, об'єкт) на основі спадкування. (припустимо, у SomeBaseClass немає інших батьків, крім об'єкта за замовчуванням)

Пропустивши Child, self, superпошук в MRO з selfпримірника і повертає проксі - об'єкт поруч від дитини, в даному випадку це SomeBaseClass, цей об'єкт потім викликає __init__метод SomeBaseClass. Іншим словом, якби він був super(SomeBaseClass,self), об'єктом проксі-сервера superбув биobject

Для багатонаступного успадкування MRO може містити багато класів, тому в основному superдозволяє вам вирішити, з чого потрібно починати пошук в MRO.


0

Розглянемо наступний код:

class X():
    def __init__(self):
        print("X")

class Y(X):
    def __init__(self):
        # X.__init__(self)
        super(Y, self).__init__()
        print("Y")

class P(X):
    def __init__(self):
        super(P, self).__init__()
        print("P")

class Q(Y, P):
    def __init__(self):
        super(Q, self).__init__()
        print("Q")

Q()

Якщо змінити конструктор Yна X.__init__, ви отримаєте:

X
Y
Q

Але використовуючи super(Y, self).__init__(), ви отримаєте:

X
P
Y
Q

І Pчи Qнавіть може бути залучений з іншого файлу , який ви не знаєте , коли ви пишете Xі Y. Отже, в основному ви не знаєте, на що super(Child, self)буде посилатися, коли ви пишете class Y(X), навіть підпис Y настільки ж просто Y(X). Ось чому супер може бути кращим вибором.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.