Что делает "супер" в Python?
в чем разница между:
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
и:
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
Я видел super используется довольно много в классах только с одним наследованием. Я понимаю, почему вы используете его в множественном наследовании, но неясно, каковы преимущества его использования в такой ситуации.
6 ответов:
преимущества
super()в одиночном наследовании минимальны - в основном, вам не нужно жестко кодировать имя базового класса в каждый метод, который использует его родительские методы.однако практически невозможно использовать множественное наследование без
super(). Это включает в себя идиомы, как миксины, интерфейсы, абстрактные классы и т. д. Это распространяется на код, который позже расширяет ваш. Если кто-то позже хотел написать класс, который продленChildи миксин, их код будет не работает должным образом.
какая разница?
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 child не удается внедрить зависимость, потому что ребенок, который вы используете, жестко закодировал метод, который будет вызван после его собственного:
>>> o = UnsuperInjector() UnsuperChild.__init__(self) called SomeBaseClass.__init__(self) calledоднако, класс с ребенком, который использует
superможно правильно ввести зависимость:>>> o2 = SuperInjector() SuperChild.__init__(self) called InjectMe.__init__(self) called SomeBaseClass.__init__(self) calledобращение к комментарию
почему в мире это было бы полезно?
Python линеаризует сложное дерево наследования через алгоритм линеаризации C3 для создания порядка разрешения метода (MRO).
мы хотим, чтобы методы были найдены в таком порядке.
для метода, определенного в родителе, чтобы найти следующий в этом порядке без
super, что бы
- получить mro от типа экземпляра
- ищите тип, который определяет метод
- найти следующий тип с помощью метода
- свяжите этот метод и вызовите его с ожидаемыми аргументами
The
UnsuperChildне должно иметь доступа кInjectMe. Почему не вывод " всегда избегайте использованияsuper"? Чего мне здесь не хватает?The
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в этом случае 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для дополнительного чтения я бы рекомендовал следующие ответы:
при вызове
super()чтобы разрешить родительскую версию classmethod, метода экземпляра или staticmethod, мы хотим передать текущий класс, область которого мы находимся в качестве первого аргумента, чтобы указать, к какой родительской области мы пытаемся разрешить, и в качестве второго аргумента объект интереса, чтобы указать, к какому объекту мы пытаемся применить эту область.рассмотрим иерархию классов
A,BиCгде каждый класс является родителем одного следуя за ним, иa,bиcсоответствующие экземпляры каждого из них.super(B, b) # resolves to the scope of B's parent i.e. A # and applies that scope to b, as if b was an instance of A super(C, c) # resolves to the scope of C's parent i.e. B # and applies that scope to c super(B, c) # resolves to the scope of B's parent i.e. A # and applies that scope to cиспользуя
superС staticmethodнапример,
super()внутри__new__()методclass A(object): def __new__(cls, *a, **kw): # ... # whatever you want to specialize or override here # ... return super(A, cls).__new__(cls, *a, **kw)объяснение:
1-хотя это обычно для
__new__()взять в качестве первого параметра ссылку на класс, называя это не реализовано в Python как classmethod, но скорее staticmethod. То есть ссылка на класс должна быть передана явно в качестве первого аргумента при вызове__new__()напрямую:# if you defined this class A(object): def __new__(cls): pass # calling this would raise a TypeError due to the missing argument A.__new__() # whereas this would be fine A.__new__(A)2 - при вызове
super()чтобы попасть в родительский класс мы передаем дочерний классAв качестве первого аргумента мы передаем ссылку на интересующий объект, в этом случае это ссылка на класс, которая была передана, когдаA.__new__(cls)называлась. В большинстве случаев это также ссылка на дочерний класс. В некоторых ситуациях это может быть, например, в случай наследования нескольких поколений.super(A, cls)3-так как по общему правилу
__new__()- это staticmethod,super(A, cls).__new__также вернет staticmethod и должен быть предоставлен все аргументы явно, включая ссылку на объект insterest, в этом случаеcls.super(A, cls).__new__(cls, *a, **kw)4-делать то же самое без
superclass 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С classmethodclass A(object): @classmethod def alternate_constructor(cls, *a, **kw): print "A.alternate_constructor called" return cls(*a, **kw) class B(A): @classmethod def alternate_constructor(cls, *a, **kw): # ... # whatever you want to specialize or override here # ... print "B.alternate_constructor called" return super(B, cls).alternate_constructor(*a, **kw)объяснение:
1-classmethod может быть вызван из класса напрямую и принимает в качестве своего первого параметра ссылку на класс.
# calling directly from the class is fine, # a reference to the class is passed implicitly a = A.alternate_constructor() b = B.alternate_constructor()2 - при вызове
super()в classmethod для разрешения его родительской версии мы хотим передать текущий дочерний класс в качестве первого аргумента, чтобы указать, какую родительскую область мы пытаемся чтобы решить, и объект интереса в качестве второго аргумента, чтобы указать, к какому объекту мы хотим применить эту область, которая в целом является ссылкой на сам дочерний класс или один из его подклассов.super(B, cls_or_subcls)3 - вызов
super(B, cls)разрешает областьAи применяет его кcls. Так какalternate_constructor()это classmethod вызовsuper(B, cls).alternate_constructor(...)будет неявно передавать ссылкуclsкак первый аргумент кA'S версияalternate_constructor()super(B, cls).alternate_constructor()4 - сделать то же самое без использования
super()вам нужно будет получить ссылку на unbound версияA.alternate_constructor()(т. е. явная версия функции). Просто сделать это не получится:class B(A): @classmethod def alternate_constructor(cls, *a, **kw): # ... # whatever you want to specialize or override here # ... print "B.alternate_constructor called" return A.alternate_constructor(cls, *a, **kw)выше не будет работать, потому что
A.alternate_constructor()метод принимает неявную ссылку наAкак его первый аргумент. Элемент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 (порядок разрешения метода) находится в порядке (Child, SomeBaseClass, object) на основе наследования. (предположим, что SomeBaseClass не имеет других родителей, кроме объекта по умолчанию)
, передав
Child, self,superпоиск в МРОselfэкземпляр и вернуть прокси-объект рядом с дочерним, в этом случае это SomeBaseClass, этот объект затем вызывает__init__метод SomeBaseClass. Другими словами, если этоsuper(SomeBaseClass,self)прокси-объект, которыйsuperотдача будетobjectдля множественного наследования MRO может содержать много классов, поэтому в основном
superпозволяет решить, где вы хотите начать поиск в МРО.
Comments