Differences between revisions 13 and 14
Revision 13 as of 2020-11-10 18:47:32
Size: 17396
Editor: FrBrGeorge
Comment:
Revision 14 as of 2020-11-10 20:40:43
Size: 18800
Editor: FrBrGeorge
Comment:
Deletions are marked like this. Additions are marked like this.
Line 405: Line 405:
 1. `<<EJCMC(148, SubString, Строки с вычитанием)>>`
 Реализовать класс `SubString`, который бы полностью воспроизводил поведение `str`, но вдобавок бы поддерживал операцию вычитания строк. Вычитание устроено так: «уменьшаемое» просматривается посимвольно, и если соответствующий символ присутствует в «вычитаемом», то он однократно удаляется из обеих строк.
  * К моменту прохождения теста ничего, кроме класса `SubString` в глобальном пространстве имён быть не должно
  {{{#!highlight python
print(SubString("qwertyerty")-SubString("ttttr"))
  }}}
  * Подсказка 1: унаследоваться от `str` можно, но (кажется), так задачу решить нельзя, ищите дальше
  * Подсказка 2: операции вида `строка1+=строка2` и всякие манипуляции с секциями строк могут оказаться слишком тяжелыми для тестов (но это неточно)
  * (тут будет спойлер)
  {{{
qweyery
  }}}

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

Полиморфизм в случае 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"

    ???
    • # Ошибок нет
      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
    • В этом примере сначала приведён ввод, затем вывод (так и будет при тестировании), но если запустить вручную они перемешиваются
      • Я делал так:
           1 $ mkfifo BoldCalc
           2 $ python3 BoldCalc.py > BoldCalc
           3 … ввод
           4 
        
      и в другом окне
         1 $ cat BoldCalc
         2 … вывод
         3 
      
  • <<EJCMC(148, SubString, Строки с вычитанием)>> Реализовать класс SubString, который бы полностью воспроизводил поведение str, но вдобавок бы поддерживал операцию вычитания строк. Вычитание устроено так: «уменьшаемое» просматривается посимвольно, и если соответствующий символ присутствует в «вычитаемом», то он однократно удаляется из обеих строк.

    • К моменту прохождения теста ничего, кроме класса SubString в глобальном пространстве имён быть не должно

         1 print(SubString("qwertyerty")-SubString("ttttr"))  
      
    • Подсказка 1: унаследоваться от str можно, но (кажется), так задачу решить нельзя, ищите дальше

    • Подсказка 2: операции вида строка1+=строка2 и всякие манипуляции с секциями строк могут оказаться слишком тяжелыми для тестов (но это неточно)

    • (тут будет спойлер)
      qweyery  

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