Наследование и исключения

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

Просто:

   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()

Вызов методов базового класса:

   1 class A:
   2     def fun(self):
   3         return "A"
   4 
   5 class B(A):
   6     def fun(self):
   7         return super().fun()+"B"

Защита от коллизии имён

   1 >>> class C:
   2 ...     __A=1
   3 ...
   4 >>> dir(C)
   5 ['_C__A', '__class__', '__delattr__', …
   6 

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

Линеаризация

Создание линейного списка родительских классов для поиска полей в нём (Method Resolution Order, MRO).

MRO C3

Общий принцип: обход дерева в ширину, при котором

Описание:

Алгоритм

Пример

   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

Но если (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 

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

   1 class A: pass
   2 class B(A): pass
   3 class X(A, B): pass
   4 class Y(B, A): pass

super():

   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__

Полиморфизм

Полиморфизм в случае duck typing всего один, зато тотальный!

Про полиморфизм — всё ☺.

(На самом деле — нет, всё это ещё понадобится в случае статической типизации).

Проксирование

Хранить родительский объект в виде поля, а все методы нового класса делать обёрткой вокруг методов родительского объекта.

Исключения

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

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

Оператор try:

Управление вычислениями

Исключение — это не «ошибка», а нелинейная передача управления

TODO

   1 from math import inf
   2 
   3 
   4 def divisor(a, b):
   5     return a/b
   6 
   7 
   8 def proxy(fun, *args):
   9     try:
  10         return fun(*args)
  11     except ZeroDivisionError:
  12         return inf
  13 
  14 
  15 for i in range(-2, 3):
  16     print(proxy(divisor, 100, i))

Оператор `raise`

Исключения — не «ошибки», а способ обработки некоторых условий не там, где они были обнаружены.

Пример:

   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

Вариант raise from явная подмена или удаление причины двойного исключения. См. документацию

Д/З

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

TODO

  1. EJudge: DivStr 'Строка с делением'

    Написать класс DivStr, унаследованный от str, который поддерживал бы операцию деления «//» и остатка от деления «%». Деление на N должно возвращать список из N подстрок одинаковой наибольшей длины, на которые можно разделить исходную строку, а остаток — оставшуюся концевую подстроку меньшей длины (возможно, пустую).

    • Дополнительное требование: все пользовательские (не начинающиеся на "__") методы str, которые возвращают строку, должны возвращать экземпляр DivStr. Это же требование распространяется на методы __getitem__, __add__, __mul__ и __rmul__. См. советы и комментарии в полном условии задачи.

    Input:

       1 a = DivStr("XcDfQWEasdERTdfgRTY")
       2 print(* a // 4)
       3 print(a % 4)
       4 print(* a % 10 // 3)
       5 print(a.lower() % 3)
       6 print(* a[1:7] // 3)
    
    Output:

    XcDf QWEa sdER Tdfg
    RTY
    ERT dfg RTY
    y
    cD fQ WE
  2. EJudge: PepelaC3 'Пепелац-Ц-III'

    Пепелац собирается из отдельных деталей по инструкции. В инструкцию входит перечень подсистем, из которых состоит пепелац (не менее двух), собственных деталей пепелаца и списка всех деталей, которые требуются для сборки (он непуст). Это последняя строка инструкции. В начале инструкции идут описания самих подсистем в формате «имя_системы» «перечень подсистем» «список собственных деталей». Проверить корректность инструкции — соответствует ли она правилам. Если соответствует — вывести «Correct», в противном случае — «Incorrect».

    • Правила:

    • Имя подсистемы — одна заглавная латинская буква (проверять не надо)
    • Имя детали — одна строчная латинская буква (проверять не надо)
    • «Список» деталей или подсистем — это строка, возможно, пустая
    • В разных подсистемах могут встречаться детали с одинаковыми именами
    • {i} В каждом списке все буквы разные

    • {i} Поиск детали осуществляется по алгоритму «MRO C3»: в порядке появления подсистем в списке, но также и в том порядке, в котором они требуют детали друг из друга. Если совмещение порядков невозможно, инструкция некорректна

      • Например, если подсистеме B требуются детали из подсистемы A, но в како-то списке A идет раньше B — инструкция некорректна

    • {i} Деталь из списка необходимых должна присутствовать среди собственных или в перечисленных подсистемах

    Input:

    A abc
    B cde
    C A f
    D AB e
    DC e abcdef
    Output:

    Correct
  3. EJudge: TestFun 'Тестировщик'

    Написать класс Tester, при создании экземпляра которого ему передаётся единственный параметр — некоторая функция fun. Сам экземпляр должен быть callable, и принимать два параметра — последовательность кортежей suite и необязательная (возможно, пустая) последовательность исключений allowed. При вызове должна осуществляться проверка, можно ли функции fun() передавать каждый элемент suite в качестве позиционных параметров. Если исключений не возникло, результат работы — 0, если исключения попадали под классификацию одного из allowed, результат — -1, если же были исключения не из allowed — 1.

    Input:

       1 T = Tester(int)
       2 print(T([(12,), ("12", 16)], []))
       3 print(T([(12,), ("12", 16), ("89", 8)], [ValueError, IndexError]))
       4 print(T([(12,), ("12", 16), ("89", 8), (1, 1, 1)], [ValueError, IndexError]))
    
    Output:

    0
    -1
    1

LecturesCMC/PythonIntro2021/09_InheritaneExceptions (последним исправлял пользователь FrBrGeorge 2022-11-03 18:04:13)