Различия между версиями 16 и 17
Версия 16 от 2018-11-26 15:24:40
Размер: 6855
Редактор: FrBrGeorge
Комментарий:
Версия 17 от 2018-11-28 01:21:53
Размер: 6875
Редактор: FrBrGeorge
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 105: Строка 105:
   {{{#!pycon    {{{#!highlight pycon
Строка 120: Строка 120:
   {{{#!pycon    {{{#!highlight pycon

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

Долги за прошлый раз:

  • hasattr() и getattr()

  • __iter__()

  • __del__()

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

Объектное планирование и ООП

ООП:

Характеристика

Python3

Инкапсуляция (не сокрытие)

Иерархия пространств имён

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

Наследование + C3 MRO

Полиморфизм

«Из коробки», т. к. duck typing

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

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

TODO пример

Простое наследование

  • Видимость и перегрузка методов
  • Преобразование типов и создание новых объектов текущего типа:
    • type(self)(…)

  • Вызов метода родительского класса
    • super() — прокси-объект, аккумулирующий методы всех родительских классов (см. множественное наследование)

  • Защита полей от случайной перегрузки («__»)

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

  • Проблема ромбовидного наследования:
    • 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.
        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 
        
  • super(): как всегда — объект-прокси всех методов родительских классов, в в случае множественного наследования аналогов не имеет (это как бы объект несуществующего класса)

Д/З

  1. Повторить 15-ю и 16-ю главы учебника, прочитать и прощёлкать 17-ю и 18-ю главы учебника

    • Прочитать статьи про линеаризацию
      • Зачем в описании линеаризации участвует объединение линеаризаций родительских классов и самих классов (в лекциях я это опустил)?

      • Иными словами, чему служит выделенная часть:
        • L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)

      • Много ли мы теряем от того, что C3, в отличие от обхода дерева, может завершиться неудачей?
  2. EJudge: DivStr 'Деление строки'

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

    Input:

       1 a = DivStr("XcDfQWEasdERTdfgRTY")
       2 print(*a//4)
       3 print(a%4)
    
    Output:

    XcDf QWEa sdER Tdfg
    RTY
  3. EJudge: DefCounter 'Счётчик с умолчанием'

    (исследовательская задача) Написать класс DefCounter, унаследованный от collections.Counter, в котором значения для несуществующих элементов были бы не 0, а задавались в конструкторе именным параметром missing= (по умолчанию — -1).

    Input:

    A = DefCounter("QWEqweQWEqweQWE", missing=-10)
    print(A)
    print(A["Z"])
    A["P"]+=5
    print(A)
    Output:

    DefCounter({'Q': 3, 'W': 3, 'E': 3, 'q': 2, 'w': 2, 'e': 2})
    -10
    DefCounter({'Q': 3, 'W': 3, 'E': 3, 'q': 2, 'w': 2, 'e': 2, 'P': -5})
  4. EJudge: LetterAttr 'Буквенное поле'

    Написать класс LetterAttr, в котором будут допустимы поля с любым именем; значение каждого поля по умолчанию будет совпадать с именем поля (строка), а при задании нового строкового значения туда будут попадать только буквы, встречающиеся в имени поля.

    Input:

       1 A = LetterAttr()
       2 print(A.letter)
       3 print(A.digit)
       4 A.letter = "teller"
       5 print(A.letter)
       6 A.letter = "fortune teller"
       7 print(A.letter)
    
    Output:

    letter
    digit
    teller
    rteteller

LecturesCMC/PythonIntro2018/10_Inheritance (последним исправлял пользователь FrBrGeorge 2018-11-28 01:21:53)