9257
Комментарий:
|
19095
|
Удаления помечены так. | Добавления помечены так. |
Строка 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. Рассматриваем список (всех линеаризаций, родительские классы справа) слева направо |
Строка 91: | Строка 92: |
* Если он входит только в ''начала'' списков (или не входит), то есть не является ничьим ''предком'' и не следует ни за кем из оставшихся в списках классов | * Если он входит только в ''начала'' списков (или не входит никуда), * то есть: 1. не является ничьим ''предком'' и 1. не следует ''после'' кого-то оставшихся элементов в объявлениях классов |
Строка 97: | Строка 101: |
==== Пример ===== Пример ''значимо'' упрощённый, для полного понимания читайте документацию. |
==== Пример ==== {{attachment:MRO.svg}} |
Строка 117: | Строка 121: |
L[B] = B + merge(DO, EO) | L[B] = B + merge(DO, EO, D, E) |
Строка 119: | Строка 123: |
L[B] = B + D + merge(O, EO) | L[B] = B + D + merge(O, EO, …, E) |
Строка 122: | Строка 126: |
L[B] = B + D + E + merge(O, O) | L[B] = B + D + E + merge(O, O, …, …) |
Строка 124: | Строка 128: |
L[B] = BDEO | L[B] = B + D + E + O → BDEO |
Строка 128: | Строка 133: |
L[C] = CDFO | L[C] → CDFO |
Строка 133: | Строка 138: |
A + merge(BDEO,CDFO) | A + merge(BDEO, CDFO, B, C) |
Строка 135: | Строка 140: |
A + B + merge(DEO,CDFO) | A + B + merge(DEO, CDFO, …, C) |
Строка 137: | Строка 142: |
A + B + C + merge(DEO,DFO) | A + B + C + merge(DEO, DFO, …, …) |
Строка 139: | Строка 144: |
A + B + C + D + merge(EO,FO) | A + B + C + D + merge(EO, FO, …, …) |
Строка 141: | Строка 146: |
A + B + C + D + E + merge(O,FO) F? + A + B + C + D + E + F + merge(O,O) |
A + B + C + D + E + merge(O, FO, …, …) O? × F? + A + B + C + D + E + F + merge(O, O, …, …) |
Строка 145: | Строка 150: |
ABCDEFO | → ABCDEFO |
Строка 152: | Строка 157: |
* Но если (`B(E,D)` вместо `B(D,E)`): {{{#!python |
Но если (`B(E,D)` вместо `B(D,E)`): {{attachment:MRO2.svg}} {{{#!python |
Строка 163: | Строка 169: |
{{{#!highlight pycon | {{{#!highlight pycon |
Строка 169: | Строка 175: |
Соответственно, нет решения для: |
(проверьте!) Соответственно, нет решения для X, но есть для Y: {{attachment:MROx.svg}} |
Строка 174: | Строка 181: |
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] |
class X(A, B): pass class Y(B, A): pass }}} * Y: `Y + [BA, B, A]` = `YBA` * X: `X + [AB, B, A]` :( , невозможно выбрать очередной элемент (на самом деле — потому что порядок объявления AB конфликтует с порядком наследования BA, но это не всегда так очевидно) |
Строка 185: | Строка 189: |
* в случае множественного наследования аналогов не имеет | * в случае множественного наследования аналогов не имеет |
Строка 234: | Строка 238: |
== Исключения == Исключения – это механизм управления вычислительным потоком, который завязан на разнесении по коду проверки свойств данных и обработки результатов этой проверки. Синтаксическая ошибка `SyntaxError` — не обрабатывается (ещё такие ошибки?) Оператор `try`: * Клауза `except` * Вариант `except Исключение` * Исключения — объекты Python3 (унаследованы от `BaseException`) * Дерево исключений, перехват всех дочерних * Собственные исключения (унаследованы от `Exception`, а не `BaseException` — некоторые исключения перехватывать не стоит) {{{#!python class B(Exception): pass class C(B): pass class D(C): pass for cls in [B, C, D]: try: raise cls() except D: print("D") except C: print("C") except B: print("B") }}} А теперь по переставляем пары строк `except … print()` :) * Вариант `except Исключение as идентификатор`, произвольные параметры исключения * Клауза `else:` — если исключений не было * Клауза `finally:` — выполняется даже если исключение не перехвачено ==== Оператор `raise` ==== * вариант `raise Exception` vs. `raise Exception(параметры)` — по идее `Exception` — это класс, а `Exception()` — объект, но на самом деле при выходе исключения всё равно изготавливается объект Исключения — не «ошибки», а способ обработки некоторых условий ''не там'', где они были обнаружены. Пример: {{{#!highlight python3 class Exc1(Exception): pass class Exc2(Exception): pass def funerr(a,b): if a<b: raise Exc1("A must be greater than B") return a//b def justfun(a,b): if a<b: raise Exc2("A must be greater than B") c = funerr(2*a, 3*b) return c for a,b in (10,3),(5,5),(10,0): try: c = justfun(a,b) except Exc1: c = -1 except Exc2: c = -2 except ZeroDivisionError: c = -3 print(c) }}} На следующую лекцию: локальность имени в операторе `as`: {{{#!highlight python try: raise Exception("QQ!", "QQ!", "QQ-QRKQ.") except Exception as E: print(F:=E) print( f"{F=}" if "F" in globals() else "No F") print( f"{E=}" if "E" in globals() else "No E") }}} {{{ ('QQ!', 'QQ!', 'QQ-QRKQ.') F=Exception('QQ!', 'QQ!', 'QQ-QRKQ.') No E }}} |
|
Строка 235: | Строка 325: |
1.#0 Прочитать: * Про C3 MRO [[https://habr.com/post/62203/|на Хабре]] и [[https://www.python.org/download/releases/2.3/mro/|в документации Python]] * Про исключения [[py3tut:errors|в учебнике]] и в справочнике [[py3ref:executionmodel.html#exceptions|про исключения]], [[py3ref:compound_stmts.html#the-try-statement|try]] и [[py3ref:simple_stmts.html#the-raise-statement|raise]] |
|
Строка 236: | Строка 329: |
1.`<<EJCMC(148, MroC3, MRO C3)>>` Написать программу, на вход которой подаётся синтаксически верный код на ЯП Python, без пустых строк и многострочных констант; пустая только последняя строка. В этом коде * Могут быть определены некоторые классы с помощью оператора `class` на верхнем уровне программы (т. е. не внутри классов/функций) * Имена классов не меняются (т. е. после `class C: …` никогда не бывает `C = …`) * В наследовании используются только уже определённые в этом коде классы На выходе программа должна отчитаться, допустимо ли наследование, которое (возможно) встретилось в коде, и вывести "`Yes`" или "`No`". {{{ class A: B = 0 class B(A): A = 4 class C(A, B): A = B = C = 5 }}} {{{ No }}} * Подсказка: предполагается, что есоли вы встретили в начале строки слово "`class `" — это настоящее описание класса 1. `<<EJCMC(148, BoldCalc, Надёжный калькулятор)>>` Написать программу — калькулятор с переменными и обработкой ошибок * Пробелы в строках игнорируются * Команда, начинающаяся на '`#`' — комментарий, такие команды игнорируются * Команда вида `Идентификатор = выражение` задаёт переменную `Идентификатор` (именование как в Python) * Если слева от "`=`" стоит не идентификатор, выводится `"Assignment error`"; всё, что справа, игнорируется * Команда вида `выражение` выводит значение выражения. * Выражение — это арифметическое выражение, состоящее из * ''целых'' чисел * уже определённых идентификаторов * круглых скобок * действий `+`, `-`, `*`, `/`, `%` и унарных `+` и `-`. * Деление ''целочисленное'' * Любое другое выражение приводит к выводу ошибки "`Syntax error`" * Если выражение нельзя вычислить, потому что в нём встречаются неопределённые переменные, выводится ошибка "`Name error`" ??? {{{ # Ошибок нет 234 10/3 A = 3*(2+(1-7)%5) A+100 + ++ - -- - + - - 0 # Начинаются ошибки 7*B 3=3 A=4=5 A() A/0 }}} * Подсказка 1: удобно пользоваться [[py3doc:functions#eval|eval()]] ''со всеми тремя'' параметрами * есть такая в нашем случае неприятная штука — `__builtins__`, её тоже следует перебить * Подсказка 2: кое-какие иные конструкции Python тоже являются синтаксическими ошибками {{{ 234 3 118 0 Name error Assignment error Syntax error Syntax error Runtime error }}} * В этом примере сначала приведён ввод, затем вывод (так и будет при тестировании), но если запустить вручную они перемешиваются * Я делал так: {{{#!highlight console $ mkfifo BoldCalc $ python3 BoldCalc.py > BoldCalc … ввод }}} и в другом окне {{{#!highlight console $ cat BoldCalc … вывод }}} 1. `<<EJCMC(148, SubString, Строки с вычитанием)>>` Реализовать класс `SubString`, который бы полностью воспроизводил поведение `str`, но вдобавок бы поддерживал операцию вычитания строк. Вычитание устроено так: «уменьшаемое» просматривается посимвольно, и если соответствующий символ присутствует в «вычитаемом», то он однократно удаляется из обеих строк. * К моменту прохождения теста ничего, кроме класса `SubString` в глобальном пространстве имён быть не должно {{{#!highlight python print(SubString("qwertyerty")-SubString("ttttr")) }}} * Подсказка 1: унаследоваться от `str` можно, но (кажется), так задачу решить нельзя, ищите дальше * Подсказка 2: операции вида `строка1+=строка2` и всякие манипуляции с секциями строк могут оказаться слишком тяжелыми для тестов (но это неточно) * (тут будет спойлер) {{{ qweyery }}} |
Наследование
Полиморфизм в случае 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, E) D? Good L[B] = B + D + merge(O, EO, …, E) O? Not good (EO) E? Good L[B] = B + D + E + merge(O, O, …, …) O? Good L[B] = B + D + E + O → BDEO
соответственно,L[C] → CDFO
наконец,L[A]: A + merge(BDEO, CDFO, B, C) B? + A + B + merge(DEO, CDFO, …, C) 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, …, …) O? × F? + A + B + C + D + E + F + merge(O, O, …, …) O? + → ABCDEFO
То есть:
Но если (B(E,D) вместо B(D,E)):
- то
- (проверьте!)
Соответственно, нет решения для X, но есть для Y:
Y: Y + [BA, B, A] = YBA
X: X + [AB, B, A] , невозможно выбрать очередной элемент (на самом деле — потому что порядок объявления AB конфликтует с порядком наследования BA, но это не всегда так очевидно)
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__()
Исключения
Исключения – это механизм управления вычислительным потоком, который завязан на разнесении по коду проверки свойств данных и обработки результатов этой проверки.
Синтаксическая ошибка 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)
На следующую лекцию: локальность имени в операторе as:
('QQ!', 'QQ!', 'QQ-QRKQ.') F=Exception('QQ!', 'QQ!', 'QQ-QRKQ.') No E
Д/З
- Прочитать:
Про C3 MRO на Хабре и в документации Python
Про исключения в учебнике и в справочнике про исключения, try и raise
TODO
1.<<EJCMC(148, MroC3, MRO C3)>> Написать программу, на вход которой подаётся синтаксически верный код на ЯП Python, без пустых строк и многострочных констант; пустая только последняя строка. В этом коде
Могут быть определены некоторые классы с помощью оператора class на верхнем уровне программы (т. е. не внутри классов/функций)
Имена классов не меняются (т. е. после class C: … никогда не бывает C = …)
- В наследовании используются только уже определённые в этом коде классы
На выходе программа должна отчитаться, допустимо ли наследование, которое (возможно) встретилось в коде, и вывести "Yes" или "No".
class A: B = 0 class B(A): A = 4 class C(A, B): A = B = C = 5
No
Подсказка: предполагается, что есоли вы встретили в начале строки слово "class " — это настоящее описание класса
<<EJCMC(148, BoldCalc, Надёжный калькулятор)>> Написать программу — калькулятор с переменными и обработкой ошибок
- Пробелы в строках игнорируются
Команда, начинающаяся на '#' — комментарий, такие команды игнорируются
Команда вида Идентификатор = выражение задаёт переменную Идентификатор (именование как в Python)
Если слева от "=" стоит не идентификатор, выводится "Assignment error"; всё, что справа, игнорируется
Команда вида выражение выводит значение выражения.
- Выражение — это арифметическое выражение, состоящее из
целых чисел
- уже определённых идентификаторов
- круглых скобок
действий +, -, *, /, % и унарных + и -.
Деление целочисленное
Любое другое выражение приводит к выводу ошибки "Syntax error"
Если выражение нельзя вычислить, потому что в нём встречаются неопределённые переменные, выводится ошибка "Name error"
# Ошибок нет 234 10/3 A = 3*(2+(1-7)%5) A+100 + ++ - -- - + - - 0 # Начинаются ошибки 7*B 3=3 A=4=5 A() A/0
Подсказка 1: удобно пользоваться eval() со всеми тремя параметрами
есть такая в нашем случае неприятная штука — __builtins__, её тоже следует перебить
- Подсказка 2: кое-какие иные конструкции Python тоже являются синтаксическими ошибками
234 3 118 0 Name error Assignment error Syntax error Syntax error Runtime error
- В этом примере сначала приведён ввод, затем вывод (так и будет при тестировании), но если запустить вручную они перемешиваются
- Я делал так:
<<EJCMC(148, SubString, Строки с вычитанием)>> Реализовать класс SubString, который бы полностью воспроизводил поведение str, но вдобавок бы поддерживал операцию вычитания строк. Вычитание устроено так: «уменьшаемое» просматривается посимвольно, и если соответствующий символ присутствует в «вычитаемом», то он однократно удаляется из обеих строк.
К моменту прохождения теста ничего, кроме класса SubString в глобальном пространстве имён быть не должно
1 print(SubString("qwertyerty")-SubString("ttttr"))
Подсказка 1: унаследоваться от str можно, но (кажется), так задачу решить нельзя, ищите дальше
Подсказка 2: операции вида строка1+=строка2 и всякие манипуляции с секциями строк могут оказаться слишком тяжелыми для тестов (но это неточно)
- (тут будет спойлер)
qweyery