Наследование и переопределение __init__ в Python

129

Я читал «Погружение в Python» и в главе о классах приводится такой пример:

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

Затем автор говорит, что если вы хотите переопределить __init__метод, вы должны явно вызвать родительский элемент __init__с правильными параметрами.

  1. Что, если у этого FileInfoкласса было несколько классов-предков?
    • Должен ли я явно вызывать все __init__методы классов-предков ?
  2. Кроме того, должен ли я сделать это с любым другим методом, который я хочу переопределить?
liewl
источник
3
Обратите внимание, что перегрузка - это отдельная концепция от переопределения.
Dana the Sane

Ответы:

158

Книга немного устарела в отношении вызова подкласса-суперкласса. Это также немного устарело в отношении создания подклассов встроенных классов.

Сейчас это выглядит так:

class FileInfo(dict):
    """store file metadata"""
    def __init__(self, filename=None):
        super(FileInfo, self).__init__()
        self["name"] = filename

Обратите внимание на следующее:

  1. Мы можем непосредственно подкласс встроенных классов, как dict, list, tupleи т.д.

  2. В superфункции ручки отслеживания суперкласс этого класса и вызова функций в них соответствующим образом .

С. Лотт
источник
5
мне поискать лучшую книгу / учебник?
liewl
2
Итак, в случае множественного наследования отслеживает ли super () их все от вашего имени?
Дана
dict .__ init __ (self), на самом деле, но в этом нет ничего плохого - вызов super (...) просто обеспечивает более последовательный синтаксис. (Я не уверен, как это работает для множественного наследования, я думаю, что он может найти только один суперкласс init )
Дэвид Зи
4
Назначение super () состоит в том, что он обрабатывает множественное наследование. Недостатком является то, что на практике множественное наследование по-прежнему очень легко ломается (см. < Fuhm.net/super-harmful ).
чт,
2
Да, в случае множественного наследования и базовых классов, принимающих аргументы конструктора, вы обычно вызываете конструкторы вручную.
Торстен Марек
18

В каждом классе, от которого вам нужно унаследоваться, вы можете запустить цикл каждого класса, который нуждается в init'd при инициации дочернего класса ... пример, который можно скопировать, может быть лучше понят ...

class Female_Grandparent:
    def __init__(self):
        self.grandma_name = 'Grandma'

class Male_Grandparent:
    def __init__(self):
        self.grandpa_name = 'Grandpa'

class Parent(Female_Grandparent, Male_Grandparent):
    def __init__(self):
        Female_Grandparent.__init__(self)
        Male_Grandparent.__init__(self)

        self.parent_name = 'Parent Class'

class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
#---------------------------------------------------------------------------------------#
        for cls in Parent.__bases__: # This block grabs the classes of the child
             cls.__init__(self)      # class (which is named 'Parent' in this case), 
                                     # and iterates through them, initiating each one.
                                     # The result is that each parent, of each child,
                                     # is automatically handled upon initiation of the 
                                     # dependent class. WOOT WOOT! :D
#---------------------------------------------------------------------------------------#



g = Female_Grandparent()
print g.grandma_name

p = Parent()
print p.grandma_name

child = Child()

print child.grandma_name
Ошибка кода
источник
2
Не похоже, что цикл for Child.__init__необходим. Когда я убираю его из примера, ребенок все равно печатает «Бабушка». Разве дедушка и бабушка не обрабатываются Parentклассом?
Адам
4
Я думаю, что init уже обрабатываются родительским init, не так ли?
johk95
15

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

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

STH
источник
4
Для технической полноты некоторые классы, такие как threading.Thread, будут выдавать гигантские ошибки, если вы когда-нибудь попытаетесь избежать вызова родительского init .
Дэвид Бергер,
5
Я нахожу все эти разговоры о том, что "вам не нужно вызывать конструктор базы", чрезвычайно раздражает. Вы не должны называть это на каком-либо языке, который я знаю. Все они ошибаются (или нет) во многом одинаково, не инициализируя членов. Предложение не инициализировать базовые классы во многих отношениях просто неверно. Если класс не нуждается в инициализации сейчас, он понадобится ему в будущем. Конструктор является частью интерфейса структуры класса / языковой конструкции и должен использоваться правильно. Правильное использование - вызвать его когда-нибудь в производном конструкторе. Так сделай это.
AndreasT 05
2
«Предложение не инициализировать базовые классы во многих отношениях просто неверно». Никто не предлагал не инициализировать базовый класс. Внимательно прочтите ответ. Все дело в намерении. 1) Если вы хотите оставить логику инициализации базового класса как есть, вы не переопределяете метод инициализации в производном классе. 2) Если вы хотите расширить логику инициализации из базового класса, вы определяете свой собственный метод инициализации, а затем вызываете из него метод инициализации базового класса. 3) Если вы хотите заменить логику инициализации базового класса, вы определяете свой собственный метод инициализации, не вызывая метод из базового класса.
wombatonfire 09
4

Если класс FileInfo имеет более одного класса-предка, вам обязательно следует вызвать все их функции __init __ (). Вы также должны сделать то же самое для функции __del __ (), которая является деструктором.

moinudin
источник
2

Да, вы должны вызывать __init__каждый родительский класс. То же самое касается функций, если вы переопределяете функцию, которая существует в обоих родителях.

vezult
источник