Метаклассы и аннотации

Это две совсем разные темы, если что).

Метаклассы

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

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

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

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

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

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

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

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

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

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

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

Два примера:

Аннотации

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

Duck typing:

Однако:

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

<!> Рекомендуется (на момент Python 3.11) использовать именно inspect.get_annotations().

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

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

Модуль typing

Развесистая статья на Хабре (⩽ Python3.8, однако ☺, см pep-0585)

Важно: в Python есть поддержка аннотаций, но в синтаксисе нет их использования.

⇒ В язык не входит, делайте сами.

MyPy

А вот чем помимо прочего занимается Гвидо в M$

Ещё раз: зачем аннотации?

http://www.mypy-lang.org: статическая типизация в Python (ну, почти… или совсем!)

Пример для mypyc: крайне неэффективная реализация чисел Фибоначчи

   1 #!/usr/bin/env python3
   2 def fib(n: int) -> int:
   3     if n <= 1:
   4         return n
   5     else:
   6         return fib(n-2) + fib(n-1)

Сравнение производительности:

   1 $ python -m timeit -s 'import fib' 'fib.fib(30)'
   2 2 loops, best of 5: 178 msec per loop
   3 $ mypyc fib.p
   4 
   5 $ python -m timeit -s 'import fib' 'fib.fib(30)'
   6 20 loops, best of 5: 13.2 msec per loop
   7 

Д/З

  1. Прочитать про:
  2. EJudge: FloatFix 'Фиксированная точка'

    Написать метакласс fixed с параметром ndigits (по умолчанию 3), в котором все возвращаемые обычными (не статическими и не методами класса) методами значения округляются с помощью round() до ndigits знаков после запятой, если они вещественные по определению модуля numbers.

    Input:

       1 from fractions import Fraction
       2 from decimal import Decimal
       3 
       4 class C(metaclass=fixed, ndigits=4):
       5     def div(self, a, b):
       6         return a / b
       7 
       8 print(C().div(6, 7))
       9 print(C().div(Fraction(6), Fraction(7)))
      10 print(C().div(Decimal(6), Decimal(7)))
    
    Output:

    0.8571
    8571/10000
    0.8571428571428571428571428571
  3. EJudge: InitParam 'Параметры по умолчанию'

    Написать метакласс init, который рассчитывает на то, что методы создаваемого им класса полностью аннотированы. Для каждого позиционного параметра обычного метода в этом классе предусматривается значение по умолчанию (если оно не было задано) на основании типа в аннотации.

    • Если в аннотации тип параметра простой, значение по умолчанию — это тип_пареметра()

    • Если в аннотации тип параметра составной (тип_контейнера[ещё типы], например, list[int]), значение по умолчанию — это тип_контейнера()

      • Будем считать что тип самой аннотации при этом всегда types.GenericAlias

    • Если объект соответствующего типа нельзя создать конструктором без операндов, значение по умолчанию — None

    Input:

       1 class C(metaclass=init):
       2     def __init__(self, var: int, rng: range, lst: list[int], defined: str = "defined"):
       3         self.data = f"{var}/{rng}/{lst}/{defined}"
       4 
       5 for c in (C(), C(1, range(3)), C(rng=range(4, 7)), C(lst=[1, 2, 3], defined=3)):
       6     print(c.data)
    
    Output:

    0/None/[]/defined
    1/range(0, 3)/[]/defined
    0/range(4, 7)/[]/defined
    0/None/[1, 2, 3]/3

LecturesCMC/PythonIntro2022/12_MetaclassAnnotations (последним исправлял пользователь FrBrGeorge 2022-12-07 00:00:26)