Метаклассы и сопоставление шаблону

Это две совсем разные темы, если что). Или три, если успеем «Введение в аннотации». TODO А успеем ли?

Не-метаклассы

Частые приёмы программирования:

Метаклассы

Предуведомление: Тим Петерс про метаклассы ☺.

Посылка: в питоне всё — объект. Объекты-экземпляры класса конструируются с помощью вызова самого класса. А кто конструирует класс? Мета-класс!

Хороший пример real-life кода на Python, эксплуатирующий метаклассы и многое другое:

Итак, что уже и так может служить конструктором класса?

Зачем тогда нужны ещё отдельные конструкторы классов?

  1. Чёткого ответа нет.
  2. Чтобы закрыть дурную бесконечность (кто конструирует конструктор?) — но это ответ на вопрос «почему?», а не «зачем?»
  3. Чтобы отделить иерархию классов, которой пользуется программист, от того, как конструируется сам базовый класс этой иерархии

    • «Тонкая настройка» класса к моменту его создания уже произошла, и в самом классе этих инструментов нет

    • ⇒ более чистый mro(), чем в случае наследования

    • ⇒ Два похоже работающих класса с общим метаклассом не имеют общего предка
  4. Чтобы сами метаклассы тоже можно было организовывать в виде дерева наследования

Использование type()

Подробности:

Общая картина:

Два примера:

Сопоставление шаблону

Базовая статья: pep-636 (а также pep-635 и pep-634)

Главная сложность: конструкция match … case имеет отличный от Python синтаксис! Спасибо смене парсера с LL(1) на PEG.

Пересказ tutorial:

Введение в аннотации

Базовая статья: О дисциплине использования аннотаций

Duck typing:

Однако:

Поэтому нужны указания о типе полей классов, параметрах и возвращаемых значений функций/методов и т. п. — Аннотации (annotations)

Пример аннотаций полей (переменных), параметров и возвращаемых значений

Составные и нечёткие типы

составные типы:

Более полная лекция по использованию аннотаций для статической типизации в Python планируется в допглавах магистерского курса.

Д/З

  1. Прочитать про:
  2. EJudge: RndSwissknife 'Случайности'

    Написать с использованием оператора match / case функцию rnd() от двух параметров (a и b=None), которая возвращает некоторое случайное значение в зависимости от того, какого типа и в каком количестве переданы ей параметры:

    1. целое → случайное целое в диапазоне [0…a]

    2. два целых → случайное целое в диапазоне [a…b]

    3. вещественное и целое/вещественное → случайное вещественное в диапазоне [a…b[

    4. индексируемая или итерируемая последовательность → случайный элемент a

    5. индексируемая или итерируемая последовательность и целое → список из b случайных элементов a

    6. строка и целое → случайная подстрока a длиной b

    7. строка → случайное слово из a.split()

    8. строка и строка → случайная строка из a.split(b)

    В решении важно использовать только необходимые функции random, иначе тесты не пройдут.

    Input:

       1 import random
       2 random.seed(123)
       3 print(*(rnd(3, 5) for i in range(11)))
       4 prnit(rnd(5))
       5 print(*(round(rnd(3., 5), 4) for i in range(4)))
       6 print(*(rnd("Substring", 4) for i in range(4)))
       7 print(*(rnd("We won oewn wow") for i in range(5)))
       8 print(*(rnd("We won oewn wow", "wo") for i in range(4)))
       9 print(*(rnd(range(10)) for i in range(12)))
      10 print(*(rnd({1, 3, 5, 7}) for i in range(12)))
      11 print(*(rnd(range(10), 3) for i in range(4)))
      12 print(*(rnd(enumerate("qwe"), 1) for i in range(5)))
    
    Output:

    3 4 3 4 4 3 3 4 5 5 4
    2
    4.7042 3.3193 3.6744 3.6676
    ubst ubst Subs stri
    We wow We We oewn
    w n oewn  We  We
    1 2 2 0 4 6 9 7 4 7 0 4
    5 7 3 5 1 7 7 7 7 5 1 3
    [7, 0, 4] [2, 3, 5] [8, 7, 3] [7, 0, 5]
    [(2, 'e')] [(1, 'w')] [(2, 'e')] [(2, 'e')] [(1, 'w')] 
  3. EJudge: MatchSquare 'Прямоугольный класс'

    Напишите (в очередной раз ☹) класс Square(x, y, w) со следующими свойствами:

    • x и y — это координаты левой нижней вершины квадрата, а w — его ширина

    • Дополнительно поддерживаются поля h (равное w), s (равное площади квадрата) и center (кортеж с координатами середины квадрата)

    • Все поля, кроме s, можно менять

      • попытка изменить площадь ни к чему не приводит ничего не происходит
      • изменение центра или координат вершины влияют друг на друга, а ширина и высота остаются прежней
      • изменение ширины или высоты не влияют на координаты вершины, но сдвигают центр
    • Поле center должно поддерживать операцию добавления кортежа из двух чисел — это смещение середины квадрата

    • При сопоставлении в операторе match / case все эти поля можно использовать как именные параметры, а x, y и w — как позиционные

    Input:

       1 for x, y, w in (1, 2, 0), (1, 1, 7), (3, 4, 10), (5, 3, 6):
       2     Sq = Square(x, y, w)
       3     Sq.center += -1, -1
       4     match Sq:
       5         case Square(_, _, 0):
       6             print("Zero square")
       7         case Square(0, 0, _):
       8             print("Started from 0")
       9         case Square(s=100):
      10             print("10x10 square")
      11         case Square(center=c) if c[0] == round(c[0]) and c[1] == round(c[1]):
      12             print("Even-sized square")
    
    Output:

    Zero square
    Started from 0
    10x10 square
    Even-sized square
  4. EJudge: AnnoDoc 'Документация в аннотациях'

    написать мета-класс AnnoDoc, который будет добавлять в произведённые им классы такое свойство:

    • если data-атрибут класса аннотирован, и эта аннотация — строка, она считается его документацией
      • если у класса уже есть документация — добавляется туда с новой строки
      • если у класса не было документации — становится первой строкой документации класса
    • если у аннотированного строкой атрибута есть значение, тип этого значения становится его аннотацией
    • если аннотированный строкой атрибут ещё не задан, аннотация удаляется

    Модифицировать поля .__doc__ и .__annotations__ (в обход inspect) разрешается.

    Input:

       1 class C(metaclass=AnnoDoc):
       2     """This is a class"""
       3     a: "A variable" = 42
       4     b: "Undefiled field"
       5     с: int = 100500 
       6 
       7 print(C.__doc__)
       8 print(C.__annotations__)
    
    Output:

    This is a class
    a: A variable
    b: Undefiled field
    {'a': <class 'int'>, 'с': <class 'int'>}

LecturesCMC/PythonIntro2024/12_MetaclassMatch (последним исправлял пользователь FrBrGeorge 2024-12-01 21:46:28)