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

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

Метаклассы

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

Итак.

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

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

Два примера:

Аннотации

Duck typing:

Однако:

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

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

Python 3.9 с нами :)

Просто прочитаем What’s New In Python 3.9

Модуль typing

Отложенная аннотация: pep-0563

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

MyPy

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

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

Пример для mypyc

   1 import time
   2 from typing import Tuple
   3 
   4 def fb(x:int,y:int)->Tuple[int,int]:
   5     return y,x+y
   6 
   7 def test()->float:
   8     x:int=0
   9     y:int=1
  10     t:float=time.time()
  11     for i in range(1000000):
  12         x = 0
  13         y = 1
  14         for j in range(100):
  15             x,y=fb(x,y)
  16     return time.time()-t

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

   1 $ mypyc speed.py
   2 running build_ext
   3 building 'speed' extension
   4 creating build/temp.linux-x86_64-3.8
   5 creating build/temp.linux-x86_64-3.8/build
   6 x86_64-alt-linux-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -pipe -frecord-gcc-switches -Wall -g -O3 -fPIC -I/home/george/.local/lib/python3/site-packages/mypyc/lib-rt -I/usr/include/python3.8 -c build/__native.c -o build/temp.linux-x86_64-3.8/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 -pthread -shared build/temp.linux-x86_64-3.8/build/__native.o -L/usr/lib64 -o /home/george/src/mypyex/speed.cpython-38.so
   8 $ mv speed.py speed_.py
   9 $ ls
  10 build  ex1.py  __pycache__  speed.cpython-38.so  speed_.py
  11 $ python3 -c "import speed_; print(speed_.test())"
  12 12.719617366790771
  13 $ python3 -c "import speed; print(speed.test())"
  14 2.643144130706787
  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/PythonIntro2020/13_MetaclassAnnotations (last edited 2020-12-06 15:50:33 by FrBrGeorge)