Differences between revisions 10 and 11
Revision 10 as of 2020-11-10 12:49:10
Size: 9645
Editor: FrBrGeorge
Comment:
Revision 11 as of 2020-11-10 17:44:08
Size: 16669
Editor: FrBrGeorge
Comment:
Deletions are marked like this. Additions are marked like this.
Line 92: Line 92:
   * Если он входит только в ''начала'' списков (или не входит), то есть не является ничьим ''предком'' и не следует ни за кем из оставшихся в списках классов    * Если он входит только в ''начала'' списков (или не входит никуда),
    *
то есть:
     1.
не является ничьим ''предком'' и
     1.
не следует ''после'' кого-то оставшихся элементов в объявлениях классов
Line 99: Line 102:
Пример ''значимо'' упрощённый, для полного понимания читайте документацию.
Line 119: Line 121:
L[B] = B + merge(DO, EO) L[B] = B + merge(DO, EO, D, E)
Line 121: Line 123:
L[B] = B + D + merge(O, EO) L[B] = B + D + merge(O, EO, …, E)
Line 124: Line 126:
L[B] = B + D + E + merge(O, O) L[B] = B + D + E + merge(O, O, …, …)
Line 126: Line 128:
L[B] = BDEO L[B] = B + D + E + O
→ B
DEO
Line 130: Line 133:
L[C] = CDFO L[C] CDFO
Line 135: Line 138:
A + merge(BDEO,CDFO) A + merge(BDEO, CDFO, B, C)
Line 137: Line 140:
A + B + merge(DEO,CDFO) A + B + merge(DEO, CDFO, …, C)
Line 139: Line 142:
A + B + C + merge(DEO,DFO) A + B + C + merge(DEO, DFO, …, …)
Line 141: Line 144:
A + B + C + D + merge(EO,FO) A + B + C + D + merge(EO, FO, …, …)
Line 143: Line 146:
A + B + C + D + E + merge(O,FO) A + B + C + D + E + merge(O, FO, …, …)
Line 145: Line 148:
A + B + C + D + E + F + merge(O,O) A + B + C + D + E + F + merge(O, O, …, …)
Line 147: Line 150:
ABCDEFO ABCDEFO
Line 172: Line 175:

Соответственно, нет решения для:
 (проверьте!)
Соответственно, нет решения для X, но есть для Y:
 {{attachment:MROx.svg}}
Line 177: Line 181:
class X(A,B): pass
}}}
 * Потому что A, [B] A → ?
   X [A] [B, A] [A, B] ?
Зато есть для `class Y(B, A): …`:
 * Потому что A, [B] A → BA
   Y [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, но это не всегда так очевидно)
Line 236: Line 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
}}}
Line 237: Line 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]]
Line 238: Line 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
}}}
 1. `<<EJCMC(148, BoldCalc, Надёжный калькулятор)>>`
 Написать программу — калькулятор с переменными и обработкой ошибок
  * Пробелы в строках игнорируются
  * Команда, начинающаяся на '`#`' — комментарий, такие команды игнорируются
  * Команда вида `Идентификатор = выражение` задаёт переменную `Идентификатор` (именование как в Python)
   * Если слева от "`=`" стоит не идентификатор, выводится `"Assignment error`"; всё, что справа, игнорируется
  * Команда вида `выражение` выводит значение выражения.
  * Выражение — это арифметическое выражение, состоящее из
   * ''целых'' чисел
   * уже определённых идентификаторов
   * круглых скобок
   * действий `+`, `-`, `*`, `/`, `%` и унарных `+` и `-`.
    * Деление ''целочисленное''
   * Любое другое выражение приводит к выводу ошибки "`Syntax error`"
  * Если выражение нельзя вычислить, потому что в нём встречаются неопределённые переменные, выводится ошибка "`Name error`"
 ???
  * Подсказка 1: удобно пользоваться [[py3doc:functions#eval|eval()]] ''со всеми тремя'' параметрами
   * есть такая в нашем случае неприятная штука — `__builtins__`, её тоже следует перебить
  * Подсказка 2: кое-какие иные конструкции Python тоже являются синтаксическими ошибками

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

Полиморфизм в случае 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. не является ничьим предком и

          2. не следует после кого-то оставшихся элементов в объявлениях классов

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

Пример

  • MRO.svg

   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, 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
    То есть:
       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)):

  • MRO2.svg

   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 
  • (проверьте!)

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

  • MROx.svg

   1 class A: pass
   2 class B(A): pass
   3 class X(A, B): pass
   4 class Y(B, A): pass
  • 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__() {i}

Исключения

Исключения – это механизм управления вычислительным потоком, который завязан на разнесении по коду проверки свойств данных и обработки результатов этой проверки.

Синтаксическая ошибка SyntaxError — не обрабатывается (ещё такие ошибки?)

Оператор try:

  • Клауза except

    • Вариант except Исключение

      • Исключения — объекты Python3 (унаследованы от BaseException)

      • Дерево исключений, перехват всех дочерних
      • Собственные исключения (унаследованы от Exception, а не BaseException — некоторые исключения перехватывать не стоит)

        •    1 class B(Exception):
             2     pass
             3 
             4 class C(B):
             5     pass
             6 
             7 class D(C):
             8     pass
             9 
            10 for cls in [B, C, D]:
            11     try:
            12         raise cls()
            13     except D:
            14         print("D")
            15     except C:
            16         print("C")
            17     except B:
            18         print("B")
          

        А теперь по переставляем пары строк 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:

   1 try:
   2     raise Exception("QQ!", "QQ!", "QQ-QRKQ.")
   3 except Exception as E:
   4     print(F:=E)
   5 
   6 print( f"{F=}" if "F" in globals() else "No F")
   7 print( f"{E=}" if "E" in globals() else "No E")

('QQ!', 'QQ!', 'QQ-QRKQ.')
F=Exception('QQ!', 'QQ!', 'QQ-QRKQ.')
No E

Д/З

  1. Прочитать:

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
  • <<EJCMC(148, BoldCalc, Надёжный калькулятор)>> Написать программу — калькулятор с переменными и обработкой ошибок

    • Пробелы в строках игнорируются
    • Команда, начинающаяся на '#' — комментарий, такие команды игнорируются

    • Команда вида Идентификатор = выражение задаёт переменную Идентификатор (именование как в Python)

      • Если слева от "=" стоит не идентификатор, выводится "Assignment error"; всё, что справа, игнорируется

    • Команда вида выражение выводит значение выражения.

    • Выражение — это арифметическое выражение, состоящее из
      • целых чисел

      • уже определённых идентификаторов
      • круглых скобок
      • действий +, -, *, /, % и унарных + и -.

        • Деление целочисленное

      • Любое другое выражение приводит к выводу ошибки "Syntax error"

    • Если выражение нельзя вычислить, потому что в нём встречаются неопределённые переменные, выводится ошибка "Name error"

    ???
    • Подсказка 1: удобно пользоваться eval() со всеми тремя параметрами

      • есть такая в нашем случае неприятная штука — __builtins__, её тоже следует перебить

    • Подсказка 2: кое-какие иные конструкции Python тоже являются синтаксическими ошибками

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