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

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

Метаклассы

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

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

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

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

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

Два примера:

Аннотации

Duck typing:

Однако:

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

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

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

Модуль typing

Новости о Python 3.10

(Оказалось, что не новости)

>>> import inspect >>> import typing >>> class C: ... a: "D" >>> class D: pass >>> inspect.get_annotations(C) {'a': 'D'} >>> typing.get_type_hints(C) {'a': <class 'main.D'>} }}}

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

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

MyPy

Зачем аннотации?

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

Пример для mypyc

   1 #!/usr/bin/env python3
   2 import time
   3 
   4 
   5 def fb(x: int, y: int) -> tuple[int, int]:
   6     return y, x + y
   7 
   8 
   9 def test() -> float:
  10     x: int = 0
  11     y: int = 1
  12     t: float = time.time()
  13     for i in range(1000000):
  14         x = 0
  15         y = 1
  16         for j in range(100):
  17             x, y = fb(x, y)
  18     return time.time() - t

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

   1 $ mypyc speed.py
   2 running build_ext
   3 building 'speed' extension
   4 creating build/temp.linux-x86_64-3.9
   5 creating build/temp.linux-x86_64-3.9/build
   6 x86_64-alt-linux-gcc -Wno-unused-result -Wsign-compare -DDYNAMIC_ANNOTATIONS_ENABLED=1 -DNDEBUG -g -fwrapv -O3 -Wall -pipe -frecord-gcc-switches -Wall -g -O3 -flto=auto -ffat-lto-objects -pipe -frecord-gcc-switches -Wall -g -O3 -flto=auto -ffat-lto-objects -fPIC -I/home/george/venv/_mypy/lib64/python3/site-packages/mypyc/lib-rt -I/home/george/venv/_mypy/include -I/usr/include/python3.9 -c build/__native.c -o build/temp.linux-x86_64-3.9/build/__native.o -O3 -Werror -Wno-unused-function -Wno-unused-label -Wno-unreachable-code -Wno-unused-variable -Wno-unused-command-line-argument -Wno-unknown-warning-option -Wno-unused-but-set-variable
   7 x86_64-alt-linux-gcc -shared build/temp.linux-x86_64-3.9/build/__native.o -L/usr/lib64 -o /home/george/venv/_mypy/speed.cpython-39.so
   8 $ mv speed.py speed_.py
   9 $ ls
  10 build  ex1.py  __pycache__  speed.cpython-39.so  speed_.py
  11 $ python3 -c "import speed_; print(speed_.test())"
  12 9.72582745552063
  13 $ python3 -c "import speed; print(speed.test())"
  14 2.2559256553649902
  15 

Д/З

Задача сложная, присутствует исследование документации!

  1. Прочитать про:
    • Метаклассы
    • Модуль inspect (вот тут исследование)

    • Аннотации
  2. EJudge: MyMypy 'Типизация вручную'

    Написать метакласс checked, при использовании которого в порождаемом классе проверяются соответствия типов всех аннотированных параметров и возвращаемых значений всех методов. В случае несовпадения инициируется TypeError: Type mismatch: <параметр>

    • Значения параметров по умолчанию не проверяются (для простоты)
    • Поля не проверяются, только методы
    • Предполагается, что должна быть верна проверка isinstance(объект, тип), в противном случае соответствие считается неуспешным

    • При вызове (по крайней мере, в тестах) не используются конструкции *args и **kwargs

    • Параметры проверяются (если они аннотированы, конечно) строго в следующем порядке

      1. Позиционные (переданные в кортеже и распакованные) параметры, по порядку
      2. Явно заданные именные (переданные в словаре и распакованные) параметры в порядке вызова метода
      3. Нераспакованные позиционные параметры, полученные через *args. В этом случае в строке исключения пишется args (имя, которое при описании функции принимало запакованные позиционные параметры)

      4. Нераспакованные именные параметры, полученные через **kwargs

      5. Возвращаемое значение (имя параметра "return", как в аннотации)

    Input:

       1 class E(metaclass=checked):
       2     def __init__(self, var: int):
       3         self.var = var if var%2 else str(var)
       4 
       5     def mix(self, val: int, opt) -> int:
       6         return self.var*val + opt
       7 
       8     def al(self, c: int, d:int=1, *e:int, f:int=1, **g:int):
       9         return self.var*d
      10 
      11 e1, e2 = E(1), E(2)
      12 code = """
      13 e1.mix("q", "q")
      14 e1.mix(2, 3)
      15 e2.mix(2, "3")
      16 e1.al("q")
      17 e1.al(1, 2, 3, 4, 5, 6, foo=7, bar=8)
      18 e2.al(1, 2, 3, 4, 5, 6, foo=7, bar=8)
      19 e1.al("E", 2, 3, 4, 5, 6, foo=7, bar=8)
      20 e1.al(1, "E", 3, 4, 5, 6, foo=7, bar=8)
      21 e1.al(1, 2, "E", 4, 5, 6, foo=7, bar=8)
      22 e1.al(1, 2, 3, "E", 5, 6, foo="7", bar=8)
      23 e1.al(1, f="E", d=1)
      24 e1.al(1, f=1, d="E")
      25 e1.al(1, f="E", d="1")
      26 e1.al(1, d="E", f="1")
      27 e1.al(1, e="E")
      28 e1.al(1, g="E")
      29 """
      30 
      31 for c in code.strip().split("\n"):
      32     try:
      33         res = eval(c)
      34     except TypeError as E:
      35         res = E
      36     print(f"Run: {c}\nGot: {res}")
    
    Output:

    Run: e1.mix("q", "q")
    Got: Type mismatch: val
    Run: e1.mix(2, 3)
    Got: 5
    Run: e2.mix(2, "3")
    Got: Type mismatch: return
    Run: e1.al("q")
    Got: Type mismatch: c
    Run: e1.al(1, 2, 3, 4, 5, 6, foo=7, bar=8)
    Got: 2
    Run: e2.al(1, 2, 3, 4, 5, 6, foo=7, bar=8)
    Got: 22
    Run: e1.al("E", 2, 3, 4, 5, 6, foo=7, bar=8)
    Got: Type mismatch: c
    Run: e1.al(1, "E", 3, 4, 5, 6, foo=7, bar=8)
    Got: Type mismatch: d
    Run: e1.al(1, 2, "E", 4, 5, 6, foo=7, bar=8)
    Got: Type mismatch: e
    Run: e1.al(1, 2, 3, "E", 5, 6, foo="7", bar=8)
    Got: Type mismatch: e
    Run: e1.al(1, f="E", d=1)
    Got: Type mismatch: f
    Run: e1.al(1, f=1, d="E")
    Got: Type mismatch: d
    Run: e1.al(1, f="E", d="1")
    Got: Type mismatch: f
    Run: e1.al(1, d="E", f="1")
    Got: Type mismatch: d
    Run: e1.al(1, e="E")
    Got: Type mismatch: e
    Run: e1.al(1, g="E")
    Got: Type mismatch: g

LecturesCMC/PythonIntro2021/12_MetaclassAnnotations (last edited 2021-12-24 09:10:28 by FrBrGeorge)