Различия между версиями 12 и 13
Версия 12 от 2019-12-17 11:47:52
Размер: 11004
Редактор: FrBrGeorge
Комментарий:
Версия 13 от 2020-11-10 18:17:14
Размер: 11006
Редактор: FrBrGeorge
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 284: Строка 284:
/*
'''TODO'''
 1. <<EJCMC(131, ManualMRO, Порядок наследования)>>
 */
##'''TODO'''
## 1. <<EJCMC(131, ManualMRO, Порядок наследования)>>
## */

Множественное наследование и исключения

Хороший пример real-life кода на Python, эксплуатирующий метаклассы и многое другое: enum (в частности, How are Enums different?

Немного про метаклассы:

  • Создание класса с помощью type(name, bases, dict)

       1  class C:
       2     pass
    
    • это
       1  C = type("C", (), {})
    
  • type — это просто класс такой ⇒

       1  class overtype(type):
       2     pass
       3 
       4  T = overtype("Boo", (), {})
    
  • итак, «T = overtype(…)» == «class T(metaclass=overtype): …»

  • определённые правила для __init__() и __new__()

<!> Не путать с наследованием!

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

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

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

    • 2.3.png

      • Обход в ширину добирается до 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
    • https://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path

    • https://habr.com/post/62203/

    • https://ru.wikipedia.org/wiki/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__

Исключения

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

Синтаксическая ошибка 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)

Исключения порождаются в разных местах, а обрабатываются в одном:

Оператор with (если успеем)

Например, with open("file") as f: …

Или (сначала сделать простой вариант, без __init__ и raise)

  •    1 class CM:
       2     def __init__(self, val = None):
       3         self.val = val
       4 
       5     def __enter__(self):
       6         print(">>>")
       7 
       8     def __exit__(self, *args):
       9         print("<<<",*(c for c in args if c))
      10         return self.val
      11 
      12 with CM(True) as f:
      13     print("Working")
      14     raise(SyntaxError("WTF?"))
      15 
      16 print("Done")
    

Далее см. contextlib.html

Д/З

  1. Прочитать про исключения в учебнике и в справочнике про исключения, try и raise

  2. EJudge: BoldCalc 'Надёжный калькулятор'

    Написать программу — калькулятор с переменными и обработкой ошибок

    • Команда, начинающаяся на '#' — комментарий
    • Команда вида Переменная=выражение задаёт переменную

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

    • Если команда содержит знак "=", но не является присваиванием, выводится диагностика "invalid assignment" (см. пример)
    • Если слева от "=" находится не идентификатор, выводится диагностика "invalid identifier (см. пример)"
    • В случае любых других ошибок выводится текст ошибки.

    «Выражение» — это произвольное выражение Python3, в котором вдобавок можно использовать уже определённые переменные (и только их). Пробелов в командах нет. Пустая команда или точка означает конец вычислений. Калькулятор вводит и исполняет команды по одной, тут же выводя диагностику, но в тестах это выглядит как ввод последовательности строк и вывод последовательности строк.

    Input:

    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())
    .
    Output:

    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']

LecturesCMC/PythonIntro2019/12_MultipleInheritanceExceptions (последним исправлял пользователь FrBrGeorge 2020-11-10 18:17:14)