Differences between revisions 1 and 6 (spanning 5 versions)
Revision 1 as of 2020-11-10 08:59:30
Size: 9257
Editor: FrBrGeorge
Comment:
Revision 6 as of 2020-11-10 09:44:53
Size: 9585
Editor: FrBrGeorge
Comment:
Deletions are marked like this. Additions are marked like this.
Line 61: Line 61:
...  ...
Line 74: Line 74:
* Монотонность C: [C, …, B, …, A] ⇒ D(...(C)...): [D, …, C, …, B, …, A]
* Соблюдение порядка объявления: `class C(D,E,F): …` ⇒ `[C, D, E, F, …]
* ⇒ Н
екоторые ситуации невозможны
Создание линейного списка родительских классов для поиска полей в нём ('''M'''ethod '''R'''esolution '''O'''rder, MRO).
 
* Монотонность: соблюдение порядка наследования `C`: `[C, …, B, …, A]` → `D(…, C, …)`: `[D, …, C, …, B, …, A]`
 * Соблюдение порядка объявления: `class C(D,E): …` → `[C, …, D, …, E, …]`
 * Два разных порядка
некоторые ситуации невозможны
Line 80: Line 81:
 * [[https://habr.com/post/62203/\Большая статья на Хабре]]  * [[https://habr.com/post/62203/|Большая статья на Хабре]]
Line 85: Line 86:
  1. __Совмещение__ списка   1. ''Совмещение'' списка
Line 89: Line 90:
  1. Рассматриваем список слева направо (родительские классы в конце)   1. Рассматриваем список сех линеаризаций, родительские классы справа) слева направо
Line 97: Line 98:
==== Пример ===== ==== Пример ====
Line 185: Line 186:
 * в случае множественного наследования аналогов не имеет   * в случае множественного наследования аналогов не имеет

Наследование

Полиморфизм в случае duck typing всего один, зато тотальный ☺

Наследование

Просто:

   1 class New(Old):
   2     # поля и методы, возможно, перекрывающие Old.что-то-там

Видимость:

  • Поля объекта
  • Поля класса
  • Поля базового класса

Вызов конструктора (например, для операция типа «"+"»):

   1 class A:
   2 
   3     def __add__(self, other):
   4         return type(self)(self.val + other.val)
   5 

Использование A(self.val + other.val) неправильно, т. к. подменяет тип.

   1 class B(A):
   2         pass

Какого типа должно быть B()+B()?

Родительский прокси-объект super()

  • super() возвращает пространство имён, содержащее поля базового класса

  • при создании super()-объекта не создаётся экземпляр базового класса (например, не вызывается __new__() и т. п.)

Вызов методов базового класса:

   1 class A:
   2     def fun(self):
   3         return "A"
   4 
   5 class B(A):
   6     def fun(self):
   7         return super().fun()+"B"

Защита от коллизии имён

  • Если пользователь класса перегрузил поле родительского класса, значит, он так хотел
  • Если он так не хотел, ССЗБ
  • Исключение: разработчик родительского класса не хотел, чтобы поле перегружали

    • Если оно публичное — getter/setter/deleter (потом)
    • Если оно приватное — назвать его «__что-то»

      • Поле __чтото класса какойто в действительности называется _какойто__чтото

      • Если пользователь перегрузил это имя — ССЗБ премиум-класса

   1 >>> class C:
   2 ...     __A=1
   3 ...
   4 >>> dir(C)
   5 ['_C__A', '__class__', '__delattr__', …
   6 

Множественное наследование

  • Проблема ромбовидного наследования:
    • ../../PythonIntro2019/12_MultipleInheritanceExceptions/1.1.png

      • Обход в глубину добирается до A.v раньше, чем до C.v

    • ../../PythonIntro2019/12_MultipleInheritanceExceptions/2.3.png

      • Обход в ширину добирается до A.v раньше, чем до B.v

Линеаризация

Создание линейного списка родительских классов для поиска полей в нём (Method Resolution Order, MRO).

  • Монотонность: соблюдение порядка наследования C: [C, …, B, …, A]D(…, C, …): [D, …, C, …, B, …, A]

  • Соблюдение порядка объявления: class C(D,E): …[C, …, D, …, E, …]

  • Два разных порядка ⇒ некоторые ситуации невозможны

MRO C3

Общий принцип

  • Линеаризация графа наследования классов — это
    1. Сам класс
    2. Совмещение списка

      1. линеаризаций всех непосредственных родительских классов,
      2. самих родительских классов
  • Совмещение — это упорядочивание по следующему принципу:
    1. Рассматриваем список (всех линеаризаций, родительские классы справа) слева направо
    2. Рассматриваем нулевой элемент очередного списка.
      • Если он входит только в начала списков (или не входит), то есть не является ничьим предком и не следует ни за кем из оставшихся в списках классов

        • добавляем его в линеаризацию
        • удаляем его из всех списков
        • переходим к п. 1.
    3. В противном случае переходим к следующему списку (перед этим классом в линеаризации должны быть другие)
    4. Если хороших кандидатов не нашлось, линеаризация невозможна

Пример

Пример значимо упрощённый, для полного понимания читайте документацию.

   1 O = object
   2 class F(O): pass
   3 class E(O): pass
   4 class D(O): pass
   5 class C(D,F): pass
   6 class B(D,E): pass
   7 class A(B,C): pass
  • Простое наследование (L[X] — линеаризация класса X):
    L[O] = O
    L[D] = D + O
    L[E] = E + O
    L[F] = F + O
  • Множественное наследование
    L[B] = B + merge(DO, EO)
    D? Good
    L[B] = B + D + merge(O, EO)
    O? Not good (EO)
    E? Good
    L[B] = B + D + E + merge(O, O)
    O? Good
    L[B] = BDEO
    соответственно,
    L[C] = CDFO
    наконец,
        L[A]:
    A + merge(BDEO,CDFO)
    B? +
    A + B + merge(DEO,CDFO)
    D? × C? +
    A + B + C + merge(DEO,DFO)
    D? +
    A + B + C + D + merge(EO,FO)
    E? +
    A + B + C + D + E + merge(O,FO)
    F? +
    A + B + C + D + E + F + merge(O,O)
    O? +
    ABCDEFO
    То есть:
       1 >>> A.mro()
       2 [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class 'object'>]
       3 
    
  • Но если (B(E,D) вместо B(D,E)):

       1 O = object
       2 class F(O): pass
       3 class E(O): pass
       4 class D(O): pass
       5 class C(D,F): pass
       6 class B(E,D): pass
       7 class A(B,C): pass
    
    то
       1 >>> B.mro()
       2 [<class '__main__.B'>, <class '__main__.E'>, <class '__main__.D'>, <class 'object'>]
       3 >>> A.mro()
       4 [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>]
       5 
    

Соответственно, нет решения для:

   1 class A: pass
   2 class B(A): pass
   3 class X(A,B): pass

Потому что A, [B] A → , X [A] [B, A] [A, B] ?

Зато есть для class X(B, A): …

Потому что A, [B] A → BA, X [A] [B, A] [B, A]

super():

  • как всегда — объект-прокси всех методов родительских классов
  • в случае множественного наследования аналогов не имеет
  • это как бы объект несуществующего класса, в котором проделан MRO, но ещё нет ни одного поля нашего класса

   1 class A:
   2     def __new__(cls, *args):
   3     ¦   print(f"New A: {cls}, {args}")
   4     ¦   return super().__new__(cls, *args)
   5 
   6     def f(self):
   7     ¦   return f"A: {self}"
   8 
   9     def __str__(self):
  10     ¦   return f"<{type(self).__name__}>"
  11 
  12 class B:
  13     def __new__(cls, *args):
  14     ¦   print(f"New B: {cls}, {args}")
  15     ¦   return super().__new__(cls, *args)
  16 
  17     def g(self):
  18     ¦   return f"G: {self}"
  19 
  20     def __str__(self):
  21     ¦   return f"<<{type(self).__name__}>>"
  22 
  23 class AB(A, B):
  24     def __new__(cls, *args):
  25     ¦   print(f"New: {cls}, {args}")
  26     ¦   return super().__new__(cls, *args)
  27 
  28 ab = AB()
  29 print(ab, ab.f(), ab.g())

New: <class '__main__.AB'>, ()
New A: <class '__main__.AB'>, ()
New B: <class '__main__.AB'>, ()
<AB> A: <AB> G: <AB>

Обратите внимание на вызов обоих __new__() из super().__new__

Проксирование

Хранить родительский объект в виде поля, а все методы нового класса делать обёрткой вокруг методов родительского объекта.

  • Напрямую не работает, т. к. __init__() не может перебить спецметоды

  • (т. е. работает, но спецметоды надо руками задавать)
  • Возможно, решается с помощью метаклассов и __new__() {i}

Д/З

TODO

LecturesCMC/PythonIntro2020/10_Inheritance (last edited 2020-11-21 09:52:39 by FrBrGeorge)