Множественное наследование и исключения
Хороший пример real-life кода на Python, эксплуатирующий метаклассы и многое другое: enum (в частности, How are Enums different?
Немного про метаклассы:
Создание класса с помощью type(name, bases, dict)
- это
1 C = type("C", (), {})
type — это просто класс такой ⇒
итак, «T = overtype(…)» == «class T(metaclass=overtype): …»
определённые правила для __init__() и __new__()
Не путать с наследованием!
Множественное наследование
- Проблема ромбовидного наследования:
Обход в глубину добирается до A.v раньше, чем до C.v
Обход в ширину добирается до A.v раньше, чем до B.v
Что нужно? Линеаризация:
- Монотонность C: [C, …, B, …, A] ⇒ D(...(C)...): [D, …, C, …, B, …, A]
Соблюдение порядка объявления: class C(D,E,F): … ⇒ `[C, D, E, F, …]
- ⇒ Некоторые ситуации невозможны
- MRO C3
- Общий принцип
- Линеаризация графа наследования классов — это
- Сам класс
Совмещение списка
- линеаризаций всех непосредственных родительских классов,
- самих родительских классов
- Совмещение — это упорядочивание по следующему принципу:
- Рассматриваем список слева направо (родительские классы в конце)
- Рассматриваем нулевой элемент очередного списка.
Если он входит только в начала списков (или не входит), то есть не является ничьим предком и не следует ни за кем из оставшихся в списках классов
- добавляем его в линеаризацию
- удаляем его из всех списков
- переходим к п. 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)):
то
- Простое наследование (L[X] — линеаризация класса X):
Соответственно, нет решения для:
Потому что 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__
Исключения
Исключения – это механизм управления вычислительным потоком, который завязан на разнесении по коду проверки свойств данных и обработки результатов этой проверки.
Синтаксическая ошибка SyntaxError — не обрабатывается (ещё такие ошибки?)
Оператор try:
Клауза except
Вариант except Исключение
Исключения — объекты Python3 (унаследованы от BaseException)
- Дерево исключений, перехват всех дочерних
Собственные исключения (унаследованы от Exception, а не BaseException — некоторые исключения перехватывать не стоит)
А теперь по переставляем пары строк except … print()
Вариант except Исключение as идентификатор, произвольные параметры исключения
Клауза else: — если исключений не было
Клауза finally: — выполняется даже если исключение не перехвачено
Оператор raise
вариант raise Exception vs. raise Exception(параметры) — по идее Exception — это класс, а Exception() — объект, но на самом деле при выходе исключения всё равно изготавливается объект
Исключения — не «ошибки», а способ обработки некоторых условий не так, где они были обнаружены.
Пример:
1 class Exc1(Exception): pass
2 class Exc2(Exception): pass
3
4 def funerr(a,b):
5 if a<b:
6 raise Exc1("A must be greater than B")
7 return a//b
8
9 def justfun(a,b):
10 if a<b:
11 raise Exc2("A must be greater than B")
12 c = funerr(2*a, 3*b)
13 return c
14
15 for a,b in (10,3),(5,5),(10,0):
16 try:
17 c = justfun(a,b)
18 except Exc1:
19 c = -1
20 except Exc2:
21 c = -2
22 except ZeroDivisionError:
23 c = -3
24 print(c)
Исключения порождаются в разных местах, а обрабатываются в одном:
Оператор with (если успеем)
Заранее задать finally (например, закрыть открытый файл, даже если были исключения)
протокол контекстного менеждера
.__enter__() / .__exit__(exc_type, exc_val, exc_tb)
Например, with open("file") as f: …
Или (сначала сделать простой вариант, без __init__ и raise)
Далее см. contextlib.html
Д/З
Прочитать про исключения в учебнике и в справочнике про исключения, try и raise
EJudge: BoldCalc 'Надёжный калькулятор'
Написать программу — калькулятор с переменными и обработкой ошибок
- Команда, начинающаяся на '#' — комментарий
Команда вида Переменная=выражение задаёт переменную
Команда вида выражение выводит значение выражения.
- Если команда содержит знак "=", но не является присваиванием, выводится диагностика "invalid assignment" (см. пример)
- Если слева от "=" находится не идентификатор, выводится диагностика "invalid identifier (см. пример)"
- В случае любых других ошибок выводится текст ошибки.
«Выражение» — это произвольное выражение Python3, в котором вдобавок можно использовать уже определённые переменные (и только их). Пробелов в командах нет. Пустая команда или точка означает конец вычислений. Калькулятор вводит и исполняет команды по одной, тут же выводя диагностику, но в тестах это выглядит как ввод последовательности строк и вывод последовательности строк.
42 100500//33 "Qq!"*(6-2) # Здесь ошибка 3,,5 10/(8-8) "wer"[2]+"qwe"[1] "wer"[7]+"qwe"[9] 1+(2+(3 a0=5 b0=7 # И здесь ошибка 12N=12 # И ещё где-то были a0+b0*8 c=b0//2+a0 d==100 c+d sorted(dir()) .
42 3045 Qq!Qq!Qq!Qq! invalid syntax (<string>, line 1) division by zero rw string index out of range unexpected EOF while parsing (<string>, line 1) invalid identifier '12N' 61 invalid assignment 'd==100' name 'd' is not defined ['__builtins__', 'a0', 'b0', 'c']