Успадкування та переосмислення __init__ в python


129

Я читав "Dive Into Python", і в розділі про класи він наводить такий приклад:

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

Тоді автор каже, що якщо ви хочете перекрити __init__метод, ви повинні явно зателефонувати до батьків __init__із правильними параметрами.

  1. Що робити, якщо в цьому FileInfoкласі було більше одного класу предків?
    • Чи потрібно явно називати всі __init__методи класів предків ?
  2. Крім того, чи потрібно це робити будь-якому іншому методу, який я хочу перекрити?

3
Зауважте, що перевантаження є окремим поняттям від переосмислення.
Dana 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
я повинен шукати кращу книгу / підручник?
laewl

2
Тож у випадку багаторазового спадкування супер () відстежує їх все від вашого імені?
Дана

dict .__ init __ (само), але насправді нічого поганого - виклик super (...) просто забезпечує більш послідовний синтаксис. (Я не знаю , як це працює для множинного спадкоємства, я думаю , що він може знайти тільки один суперклас Init )
David Z

4
Намір super () полягає в тому, щоб він обробляв багаторазове успадкування. Недоліком є ​​те, що на практиці багаторазове успадкування все ще порушується дуже легко (див. < Fuhm.net/super-harmful ).
sth

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
Це не здається, що для циклу в Child.__init__це потрібно. Коли я виймаю його із прикладу, я дитина все ще друкує "Бабуся". Чи не бабуся-дідусь-ініт не обробляє Parentклас?
Адам

4
Я думаю, що бабусь-дідусів-інітів вже обробляють батьківські ініти, чи не так?
johk95

15

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

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


4
Для технічної завершеності деякі класи, як, наприклад, threading.Thread, викличуть гігантські помилки, якщо ви коли-небудь спробуєте уникнути дзвінка батьківського init .
Девід Бергер

5
Я вважаю, що все це "вам не доведеться називати конструктора бази" дуже роздратовано. Вам не потрібно називати це будь-якою мовою, яку я знаю. Усі вони будуть викручуватися (чи ні) майже однаково, не ініціюючи членів. Запропонувати не ініціалізувати базові класи просто помилково в багатьох варіантах. Якщо клас не потребує ініціалізації, він буде потребувати його в майбутньому. Конструктор є частиною інтерфейсу структури класу / мовної конструкції, і його слід правильно використовувати. Це правильне використання - називати це колись у конструкторі вашого похідного. Тож зроби це.
AndreasT

2
"Запропонувати не ініціалізувати базові класи просто неправильно в багатьох способах." Ніхто не запропонував ініціалізувати базовий клас. Уважно прочитайте відповідь. Вся справа в намірі. 1) Якщо ви хочете залишити базову клас 'init логіки такою, якою є, ви не перекриваєте метод init у похідному класі. 2) Якщо ви хочете розширити логіку init з базового класу, ви визначаєте свій власний метод init, а потім викликаєте з нього базовий клас 'метод init. 3) Якщо ви хочете замінити ініціативу логіки базового класу, ви визначаєте свій власний метод init, не викликаючи той, який використовується з базового класу.
вибух вогню

4

Якщо у класі FileInfo є більше одного класу предків, то вам обов'язково слід зателефонувати всі їх функції __init __ (). Ви також повинні зробити те ж саме для функції __del __ (), яка є деструктором.


2

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

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