9257
Комментарий:
|
9585
|
Удаления помечены так. | Добавления помечены так. |
Строка 61: | Строка 61: |
... | ... |
Строка 74: | Строка 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, …]` * Два разных порядка ⇒ некоторые ситуации невозможны |
Строка 80: | Строка 81: |
* [[https://habr.com/post/62203/\Большая статья на Хабре]] | * [[https://habr.com/post/62203/|Большая статья на Хабре]] |
Строка 85: | Строка 86: |
1. __Совмещение__ списка | 1. ''Совмещение'' списка |
Строка 89: | Строка 90: |
1. Рассматриваем список слева направо (родительские классы в конце) | 1. Рассматриваем список (всех линеаризаций, родительские классы справа) слева направо |
Строка 97: | Строка 98: |
==== Пример ===== | ==== Пример ==== |
Строка 185: | Строка 186: |
* в случае множественного наследования аналогов не имеет | * в случае множественного наследования аналогов не имеет |
Наследование
Полиморфизм в случае duck typing всего один, зато тотальный ☺
Наследование
Просто:
Видимость:
- Поля объекта
- Поля класса
- Поля базового класса
Вызов конструктора (например, для операция типа «"+"»):
Использование A(self.val + other.val) неправильно, т. к. подменяет тип.
Какого типа должно быть B()+B()?
Родительский прокси-объект super()
super() возвращает пространство имён, содержащее поля базового класса
при создании super()-объекта не создаётся экземпляр базового класса (например, не вызывается __new__() и т. п.)
Вызов методов базового класса:
Защита от коллизии имён
- Если пользователь класса перегрузил поле родительского класса, значит, он так хотел
- Если он так не хотел, ССЗБ
Исключение: разработчик родительского класса не хотел, чтобы поле перегружали
- Если оно публичное — getter/setter/deleter (потом)
Если оно приватное — назвать его «__что-то»
Поле __чтото класса какойто в действительности называется _какойто__чтото
Если пользователь перегрузил это имя — ССЗБ премиум-класса
Множественное наследование
- Проблема ромбовидного наследования:
Обход в глубину добирается до A.v раньше, чем до C.v
Обход в ширину добирается до 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
В Википедии слишком коротко
статья Gaël Pegliasco тоже недлинная, но зато с исторической справкой
Описание с примерами в документации Python
Общий принцип
- Линеаризация графа наследования классов — это
- Сам класс
Совмещение списка
- линеаризаций всех непосредственных родительских классов,
- самих родительских классов
- Совмещение — это упорядочивание по следующему принципу:
- Рассматриваем список (всех линеаризаций, родительские классы справа) слева направо
- Рассматриваем нулевой элемент очередного списка.
Если он входит только в начала списков (или не входит), то есть не является ничьим предком и не следует ни за кем из оставшихся в списках классов
- добавляем его в линеаризацию
- удаляем его из всех списков
- переходим к п. 1.
- В противном случае переходим к следующему списку (перед этим классом в линеаризации должны быть другие)
- Если хороших кандидатов не нашлось, линеаризация невозможна
Пример
Пример значимо упрощённый, для полного понимания читайте документацию.
- Простое наследование (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
То есть: Но если (B(E,D) вместо B(D,E)):
то
Соответственно, нет решения для:
Потому что 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__()
Д/З
TODO