Як супер () Python працює з багаторазовим успадкуванням?


887

Я досить новий у об'єктно-орієнтованому програмуванні Python, і у мене виникають проблеми з розумінням super()функції (нові класи стилів), особливо коли мова йде про багаторазове успадкування.

Наприклад, якщо у вас є щось на кшталт:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

Що я не отримую: це Third()клас успадкує обидва конструкторські методи? Якщо так, то який із них буде запускатися з Super () і чому?

А що, якщо ви хочете запустити інший? Я знаю, що це має відношення до порядку вирішення методу Python ( MRO ).


Насправді багаторазове успадкування - це єдиний випадок, коли super()будь-яка користь. Я б не рекомендував використовувати його з класами з використанням лінійного успадкування, де це просто марно накладні витрати.
Бахсау

9
@Bachsau технічно правильний тим, що це невеликі накладні витрати, але super () є більш пітонічним і дозволяє повторно визначати фактори і змінювати код з часом. Використовуйте super (), якщо вам дійсно не потрібен метод, визначений для класу.
Пол Віпп

2
Ще одна проблема super()полягає в тому, що він змушує кожен підклас використовувати його також, тоді як, коли він не використовується super(), кожен підклас, який він може, вирішує сам. Якщо розробник, який використовує його, не знає про те, super()чи він не знає, що його використовували, можуть виникнути проблеми з mro, які дуже важко знайти.
Бахсау

Я знайшов тут практично кожну відповідь заплутаною в тій чи іншій мірі. Ви б насправді посилалися тут замість цього.
matanster

Відповіді:


709

Про це детально роз'яснюється з досить розумною деталізацією самого Гвідо в своєму дописі про блог Методичне рішення (включаючи дві попередні спроби).

У вашому прикладі Third()зателефонуйте First.__init__. Python шукає кожен атрибут у батьків класу, оскільки вони перераховані зліва направо. У цьому випадку ми шукаємо __init__. Отже, якщо ви визначаєте

class Third(First, Second):
    ...

Python почне з перегляду First, а якщо Firstатрибут не має, то він погляне на Second.

Ця ситуація стає більш складною, коли спадкування починає схрещувати шляхи (наприклад, якщо вони Firstуспадковані від Second). Прочитайте вищенаведене посилання для більш детальної інформації, але, коротко кажучи, Python намагатиметься підтримувати порядок, у якому кожен клас відображається у списку спадкування, починаючи з самого дочірнього класу.

Так, наприклад, якщо у вас були:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

МРО буде [Fourth, Second, Third, First].

До речі: якщо Python не зможе знайти узгоджений порядок вирішення методу, він створить виняток, замість того, щоб повернутися до поведінки, яка може здивувати користувача.

Відредаговано, щоб додати приклад неоднозначного MRO:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

Чи повинен Thirdбути МРО [First, Second]чи [Second, First]? Очевидних очікувань немає, і Python створить помилку:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

Редагувати: Я бачу декількох людей, які стверджують, що у прикладах вище відсутні бракує super()дзвінків, тому дозвольте мені пояснити: Суть прикладів - показати, як будується МРО. Вони не призначені для друку "першої \ n секунди \ третьої" чи будь-чого іншого. Можна, і, звичайно, потрібно пограти з прикладом, додавати super()дзвінки, бачити, що відбувається, і глибше розуміти модель успадкування Python. Але моя мета тут - зробити це просто і показати, як будується МРО. І він побудований так, як я пояснив:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)

12
Це стає цікавішим (і, мабуть, більш заплутаним), коли ви починаєте телефонувати супер () у Першому, Другому та Третьому [ pastebin.com/ezTyZ5Wa ].
gatoatigrado

52
Я вважаю, що відсутність супердзвінків у перших класах є справді великою проблемою з цією відповіддю; не обговорюючи, як / чому втрачається важливе критичне розуміння питання.
Сем Хартман

3
Ця відповідь просто неправильна. Без супер () дзвінків у батьків нічого не станеться. @ відповідь безжиття правильна.
Серін

8
@Cerin Суть цього прикладу полягає в тому, щоб показати, як будується МРО. Приклад НЕ призначений для друку "першої \ n секунди \ третьої" або будь-якого іншого. І MRO справді правильний: Четвертий .__ mro__ == (<class ' main .Fourth'>, <class ' main .Second'>, <class ' main .Third'>, <class ' main .First'>, < введіть 'об’єкт'>)
rbp

2
Наскільки я бачу, у цій відповіді відсутнє одне із запитань ОП, а саме: "А що робити, якщо ви хочете запустити інший?". Я хотів би побачити відповідь на це питання. Ми просто повинні чітко назвати базовий клас?
Рей

251

Ваш код та інші відповіді - це баггі. Вони відсутні у super()викликах у перших двох класах, які потрібні для кооперативного підкласу для роботи.

Ось фіксована версія коду:

class First(object):
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

super()Виклик знаходить наступний метод в MRO на кожен крок, тому перші і другі повинні мати це теж, в іншому випадку виконання зупиняється в кінці Second.__init__().

Ось що я отримую:

>>> Third()
second
first
third

90
Що робити, якщо цим класам потрібні різні параметри для ініціалізації?
Кальфжу

2
"кооперативний підклас"
Quant Metropolis

6
Таким чином виконуються методи init BOTH базових класів, тоді як оригінальний приклад називає лише перший init, що зустрічається в MRO. Я думаю, що мається на увазі під терміном "кооперативний підклас", але уточнення було б корисним ("Явне явне краще, ніж неявне", ви знаєте;))
Quant Metropolis

1
Так, якщо ви передаєте різні параметри для виклику методу через super, всі реалізації цього методу, що рухається MRO до об'єкта (), повинні мати сумісні підписи. Цього можна досягти за допомогою параметрів ключових слів: прийміть більше параметрів, ніж використовує метод, і ігноруйте зайві. Зазвичай це вважається некрасивим робити це, і в більшості випадків краще додавати нові методи, але init є (майже?) Унікальним як назва спеціального методу, але з визначеними користувачем параметрами.
безжиттєвий

15
Дизайн багаторазового успадкування дійсно поганий у python. Базові класи майже повинні знати, хто буде його отримувати, і скільки інших базових класів буде виведено, і в якому порядку ... інакше superабо не вдасться запуститись (через невідповідність параметрів), або він не зателефонує кілька підстав (тому що ви не написали superв одній із базових, що перериває посилання)!
Наваз

186

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

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

Ця остання частина має вирішальне значення для розуміння. Розглянемо приклад ще раз:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

Згідно з цією статтею про наказ щодо вирішення методу Гвідо ван Россума, порядок вирішення __init__розраховується (до Python 2.3), використовуючи "перехід вліво-вправо по глибині":

Third --> First --> object --> Second --> object

Після видалення всіх дублікатів, крім останнього, ми отримуємо:

Third --> First --> Second --> object

Отже, давайте слідкуємо за тим, що відбувається, коли ми інстанціюємо екземпляр Thirdкласу, наприклад x = Third().

  1. За даними МРО Third.__init__.
    • відбитки Third(): entering
    • потім super(Third, self).__init__()виконує і MRO повертає First.__init__виклик.
  2. First.__init__ виконує.
    • відбитки First(): entering
    • потім super(First, self).__init__()виконує і MRO повертає Second.__init__виклик.
  3. Second.__init__ виконує.
    • відбитки Second(): entering
    • потім super(Second, self).__init__()виконує і MRO повертає object.__init__виклик.
  4. object.__init__ виконує (в коді немає виписок про друк)
  5. виконання повертається до Second.__init__якого потім друкуєтьсяSecond(): exiting
  6. виконання повертається до First.__init__якого потім друкуєтьсяFirst(): exiting
  7. виконання повертається до Third.__init__якого потім друкуєтьсяThird(): exiting

Тут детально роз'яснюється, чому інстанція Третього () призводить до:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

Алгоритм MRO було вдосконалено з Python 2.3 і надалі добре працює у складних випадках, але я думаю, що використання "переходу вперед-зліва направо" + "видалення дублікатів очікується на останнє" все ще працює в більшості випадків (будь ласка коментар, якщо це не так). Обов’язково прочитайте допис у блозі від Guido!


6
Я досі не розумію, чому: Всередині init Першого супер (Перше, само) .__ init __ () називає ініт Другого, бо саме це диктує МРО!
користувач389955

@ user389955 Об'єкт створений типу Третій, який має всі методи init. Отже, якщо ви припускаєте, що MRO створює список усіх функцій init у визначеному порядку, з кожним супервикликом ви робите один крок вперед, поки не досягнете кінця.
Sreekumar R

15
Я думаю, що Крок 3 потребує більшого пояснення: Якщо Thirdб не успадкував Second, то super(First, self).__init__зателефонував би object.__init__і після повернення було б надруковано "спочатку". Але тому, що Thirdуспадковується від обох Firstі Secondзамість того, щоб викликати object.__init__після того, First.__init__як MRO диктує, що object.__init__зберігається лише остаточний виклик до , а друковані заяви у Firstта Secondне дістаються до object.__init__повернення. Оскільки Secondостанній дзвонив object.__init__, він повертається всередину Secondперед поверненням First.
MountainDrew

1
Цікаво, що PyCharm, здається, знає все це (його підказки говорять про те, які параметри йдуть, які дзвінки на супер. Він також має деяке поняття коваріації входів, тому він розпізнає List[subclass]як " List[superclass]if" subclass- підклас superclass( Listпоходить від typingмодуля PEP 483 IIRC).
Reb.Cabin

Приємний пост, але мені не вистачає інформації стосовно аргументів конструкторів, тобто що трапиться, якщо Другі та Перші очікують чітких аргументів? Конструктору Першого доведеться обробити деякі аргументи, а решту передати другому. Це так? Мені не здається правильним, що Перший повинен знати про необхідні аргументи для Другого.
Крістіан К.

58

Це відоме як Diamond Problem , на сторінці є запис на Python, але коротше кажучи, Python буде називати методи надкласу зліва направо.


Це не проблема Алмазу. Проблема з алмазами включає чотири класи, а питання ОП включає лише три.
Ian Goodfellow

147
objectє четвертим
GP89,

28

Це те, як я вирішив питання про наявність декількох успадкованих з різними змінними для ініціалізації та наявності декількох MixIns з одним і тим же викликом функції. Я повинен був явно додати змінні до пройдених ** kwargs та додати MixIn інтерфейс, щоб бути кінцевою точкою для супервикликів.

Ось Aрозширюваний базовий клас Bі Cє класи MixIn, які забезпечують функцію f. Aі Bобидва очікують параметр vу своїх __init__і Cочікує w. Функція fприймає один параметр y. Qуспадковує від усіх трьох класів. MixInF- це інтерфейс Mixin для Bта C.


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)

Я думаю, це, можливо, має бути окремим запитанням і відповідями, оскільки МРО є достатньо великою темою сама по собі, не вникаючи в різні аргументи різних функцій з успадкуванням (множинне успадкування - це особливий випадок).
безжиттєвий

8
Теоретично так. Практично цей сценарій з'являється щоразу, коли я стикався із спадщиною Diamond у python, тому я додав його сюди. Оскільки тут я їду щоразу, коли я не можу чисто уникнути спадкування алмазів. Ось кілька додаткових посилань на майбутніх мене: rhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/…
brent.payne

Ми хочемо - це програми з семантично значущими назвами параметрів. Але в цьому прикладі майже всі параметри названі анонімно, що ускладнить оригінальному програмісту документувати код, а іншому програмісту - прочитати код.
Артур


@ brent.payne Я думаю, що @Arthur означав, що весь ваш підхід покладається на використання параметрів args/ kwargsзамість названих параметрів.
макс

25

Я розумію, що це не відповідає безпосередньо на super()питання, але я вважаю, що це досить актуально, щоб поділитися.

Існує також спосіб безпосередньо викликати кожен успадкований клас:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

Просто зауважте, що якщо ви зробите це таким чином, вам доведеться телефонувати кожен вручну, оскільки я впевнений, Firstщо __init__()його не викликають.


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

@Trilarion Так, я був впевнений, що цього не буде. Однак я остаточно не знав, і не хотів заявляти, як ніби це зробив, хоча це було дуже малоймовірно. Це хороший момент щодо того, objectщо вас дзвонять двічі. Я про це не думав. Я просто хотів зауважити, що ви називаєте батьківські класи безпосередньо.
Seaux

На жаль, це відбувається, якщо init намагається отримати доступ до будь-яких приватних методів :(
Ерік Аронесті

21

Загалом

Якщо припустити, що все походить з object(ви самостійно, якщо цього немає), Python розраховує порядок роздільної здатності методу (MRO) на основі дерева спадкування вашого класу. MRO задовольняє 3 властивості:

  • Діти класу приходять перед батьками
  • Ліві батьки виходять перед правими батьками
  • Клас з’являється лише один раз у MRO

Якщо такого впорядкування немає, помилки Python. Внутрішня робота цього - це лінійка С3 походження класів. Про це читайте тут: https://www.python.org/download/releases/2.3/mro/

Таким чином, в обох наведених нижче прикладах це:

  1. Дитина
  2. Зліва
  3. Правильно
  4. Батьківський

Коли метод викликається, першим виникненням цього методу в МРО є той, який викликається. Будь-який клас, який не реалізує цей метод, пропускається. Будь-який виклик у superмежах цього методу викликає наступне виникнення цього методу в MRO. Отже, має значення як порядок розміщення класів у спадщині, так і куди ви ставите виклики superв методах.

З superпершим у кожному методі

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child() Виходи:

parent
right
left
child

З superостаннім у кожному методі

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child() Виходи:

child
left
right
parent

Я бачу , що ви можете отримати доступ з Leftдопомогою super()з Child. припустимо, я хочу отримати доступ Rightзсередини Child. Чи є спосіб отримати доступ Rightіз Childсупер? Або я повинен безпосередньо дзвонити Rightзсередини super?
alpha_989

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

1
Дякуємо за те, що явно згадуєте, що "Клас з’являється лише один раз у MRO". Це вирішило мою проблему. Тепер я, нарешті, розумію, як працює множинне спадкування. Комусь потрібно було згадати властивості MRO!
Тушар Вазірані

18

Щодо коментаря @ calfzhou , як завжди, ви можете використовувати **kwargs:

Приклад роботи в Інтернеті

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

Результат:

A None
B hello
A1 6
B1 5

Ви також можете використовувати їх позиційно:

B1(5, 6, b="hello", a=None)

але ви повинні пам’ятати MRO, це дійсно заплутано.

Я можу трохи дратувати, але я помітив, що люди забувають щоразу використовувати *argsі **kwargsколи вони перекривають метод, хоча це одна з небагатьох дійсно корисних і розумних використання цих «магічних змінних».


Нічого, це справді некрасиво. Прикро, що ви не можете просто сказати, до якого конкретного суперкласу ви хочете зателефонувати. Тим не менш, це дає мені ще більший стимул використовувати склад і уникати багаторазового спадкування, як чума.
Том Бусбі

15

Ще одна ще не накрита точка - це передача параметрів для ініціалізації класів. Оскільки місце призначення superзалежить від підкласу, єдиний хороший спосіб передачі параметрів - це упаковка їх разом. Тоді будьте обережні, щоб не було однакової назви параметра з різними значеннями.

Приклад:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

дає:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

Виклик суперкласу __init__безпосередньо до більш прямого присвоєння параметрів є заманливим, але не вдається, якщо superв суперкласі є якийсь виклик та / або MRO змінюється, а клас A може викликатися кілька разів, залежно від реалізації.

На закінчення: кооперативне успадкування та супер та конкретні параметри для ініціалізації працюють не дуже добре разом.


5
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

Вихід є

first 10
second 20
that's it

Виклик Третьому () визначає init, визначений у Третьому. І заклик до супер у цій процедурі викликає init, визначений у First. MRO = [Перший, Другий]. Тепер виклик super in init, визначений у First, продовжить пошук MRO і знайде init, визначений у Second, і будь-який виклик super буде потрапляти на об'єкт за замовчуванням init . Я сподіваюся, що цей приклад прояснює концепцію.

Якщо ви не телефонуєте супер з Першого. Ланцюжок зупиняється, і ви отримаєте наступний вихід.

first 10
that's it

1
це тому, що в класі спочатку ви назвали "print", а потім "super".
скелястий ци

2
це було для того, щоб проілюструвати наказ про покликання
Серадж Ахмад

4

Навчаючись, я дізнаюся щось, що називається супер () вбудованою функцією, якщо не помиляюся. Виклик функції super () може допомогти успадковуванню пройти через батьківського і «побратимів» і допоможе вам бачити чіткіше. Я все ще початківець, але люблю ділитися своїм досвідом використання цього супер () в python2.7.

Якщо ви прочитали коментарі на цій сторінці, ви почуєте Порядок вирішення методу (MRO), метод, який є функцією, яку ви написали, MRO буде використовувати схему «Глибина-перший-вліво-вправо» для пошуку та запуску. Ви можете зробити більше досліджень з цього приводу.

Додавши функцію super ()

super(First, self).__init__() #example for class First.

Ви можете з'єднати кілька екземплярів та "родин" із super (), додавши в них кожного та кожного в них. І він виконає методи, пройде їх і переконається, що ви не пропустили! Однак, додавши їх до або після, це має значення, ви дізнаєтесь, чи зробили ви навчальні піхотехнічні вправи 44. Нехай забава починається !!

Беручи приклад нижче, ви можете скопіювати та вставити та спробувати запустити його:

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

Як це працює? Екземпляр п'ятого () буде таким. Кожен крок йде від класу до класу, де додана суперфункція.

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

Батько був знайдений, і він продовжить третій та четвертий !!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

Тепер доступ до всіх класів із супер () було доступно! Батьківський клас був знайдений та виконаний, і тепер він продовжує розблоковувати функцію у спадщинах для закінчення кодів.

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

Результат програми вище:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

Для мене додавання super () дозволяє зрозуміти, як python виконує моє кодування і переконається, що спадщина може отримати доступ до методу, який я призначив.


Дякуємо за детальну демонстрацію!
Тушар Вазірані

3

Я хотів би додати те, що говорить @Visionscaper вгорі:

Third --> First --> object --> Second --> object

У цьому випадку інтерпретатор не фільтрує об'єктний клас, оскільки його дублюється, а не його, тому що Second з’являється в голові, а не з'являється у хвостовій позиції в підмножині ієрархії. Хоча об'єкт з'являється лише в хвостових положеннях і не вважається сильним положенням в алгоритмі С3 для визначення пріоритету.

Лінеаризація (mro) класу C, L (C), є

  • клас C
  • плюс злиття
    • лінеаризація його батьків P1, P2, .. = L (P1, P2, ...) і
    • список його батьків P1, P2, ..

Лінійне об’єднання відбувається шляхом вибору загальних класів, які відображаються як голова списків, а не хвіст, оскільки порядок має значення (стане зрозуміло нижче)

Лінеаризацію Третього можна обчислити так:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

Таким чином, для супер () реалізації в наступному коді:

class First(object):
  def __init__(self):
    super(First, self).__init__()
    print "first"

class Second(object):
  def __init__(self):
    super(Second, self).__init__()
    print "second"

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

стає очевидним, як вирішиться цей метод

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"

"радше це тому, що Другий з’являється в голові та не відображається в позиції хвоста в підмножині ієрархії." Незрозуміло, що таке голова чи хвіст, а також що таке підмножина ієрархії чи до якого підмножини ви посилаєтесь.
OrangeSherbet

Положення хвоста стосується класів, які є вищими в ієрархії класів і навпаки. Основний клас 'об'єкт' знаходиться в кінці хвоста. Ключовим моментом для розуміння алгоритму mro є те, як "Другий" видається супер "Першого". Ми зазвичай вважаємо, що це клас "об'єкт". Це правда, але лише в перспективі першого класу. Однак якщо розглядати з точки зору "третього класу", порядок ієрархії "Першого" відрізняється і обчислюється, як показано вище. алгоритм mro намагається створити цю перспективу (або підмножину ієрархії) для всіх декількох успадкованих класів
supi

3

У python 3.5+ успадкування виглядає передбачувано і дуже приємно для мене. Будь ласка, подивіться на цей код:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

Виходи:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

Як бачимо, він називає foo рівно ОДИН час для кожного успадкованого ланцюга в тому ж порядку, як і він був успадкований. Ви можете отримати це замовлення, зателефонувавши . мро :

Четвертий -> Третій -> Перший -> Другий -> База -> об'єкт


2

Можливо, є ще щось, що можна додати, невеликий приклад з Django rest_framework та декоратори. Це дає відповідь на неявне запитання: "чому я все-таки хочу цього?"

Як було сказано: ми з Django rest_framework, і ми використовуємо загальні представлення даних, і для кожного типу об’єктів у нашій базі даних ми опиняємося з одним класом перегляду, що забезпечує GET і POST для списків об'єктів, та іншим класом перегляду, що забезпечує GET , PUT та DELETE для окремих об'єктів.

Тепер POST, PUT і DELETE ми хочемо прикрасити Django з login_required. Зверніть увагу, як це стосується обох класів, але не всіх методів в обох класах.

Рішення може перейти через багаторазове успадкування.

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

Так само і для інших методів.

У списку спадку моїх конкретних класів я б додав свої LoginToPostдо ListCreateAPIViewі LoginToPutOrDeleteраніше RetrieveUpdateDestroyAPIView. Мої конкретні заняття " getзалишалися б незатребуваними.


1

Опублікував цю відповідь для мого майбутнього реферату.

Python Multiple Inheritance має використовувати алмазну модель, і підпис функції не повинен змінюватися в моделі.

    A
   / \
  B   C
   \ /
    D

Фрагмент зразкового коду буде; -

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

Тут клас А є object


1
Aповинні також бути викликом __init__. Aне "винайшов" метод __init__, тож не можна припустити, що якийсь інший клас, можливо, мав Aраніше його MRO. Єдиний клас, __init__метод якого не (і не повинен) викликати, super().__init__- це object.
чепнер

Так. Ось чому я написав «А, objectможливо, я думаю, я повинен писати class A (object) : замість цього
Ахіл Над ПК

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