Различия между версиями 1 и 12 (по 11 версиям)
Версия 1 от 2020-11-30 21:04:00
Размер: 2317
Редактор: FrBrGeorge
Комментарий:
Версия 12 от 2020-12-03 12:18:17
Размер: 17585
Редактор: FrBrGeorge
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 5: Строка 5:
 * В [[py3doc:datamodel#metaclasses|справочнике]]

Хороший пример real-life кода на Python, эксплуатирующий метаклассы и многое другое: [[py3doc:enum]] (в частности, [[py3doc:enum.html#how-are-enums-different|How are Enums different?]])
 * Класс можно создать просто функцией (а можно и декоратором)
 * В [[py3ref:datamodel#metaclasses|справочнике]]
 * Внезапно развёрнутое описание [[https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python?rq=1|на StackOverflow]] ([[https://habr.com/ru/post/145835/|перевод на Хабре]])
 * [[https://sahandsaba.com/python-classes-metaclasses.html|Статья Sahand Saba]] ([[https://ru.hexlet.io/blog/posts/prodvinutyy-python-chast-3-klassy-i-metaklassy|перевод]])
 * Забойная статья [[https://breadcrumbscollector.tech/when-to-use-metaclasses-in-python-5-interesting-use-cases/|Sebastian Buczyński]] 2020 года ([[https://webdevblog.ru/kogda-ispolzovat-metaklassy-v-python-5-interesnyh-variantov-ispolzovaniya/|Перевод]])

Хороший пример real-life кода на Python, эксплуатирующий метаклассы и многое другое:
 * [[py3doc:enum]] (в частности, [[py3doc:enum.html#how-are-enums-different|How are Enums different?]])
 * [[py3doc:abc]]

Итак.
 * Класс можно создать просто функцией (aka [[WP:Monkey patch]])
 * Декоратором
Строка 22: Строка 30:
C = class('Simple', (), {'val': 42, 'getval': lambda self: self.val}) C = type('Simple', (), {'val': 42, 'getval': lambda self: self.val})
Строка 42: Строка 50:
}}}     }}}
Строка 44: Строка 52:
[[py3doc:datamodel#metaclasses|Подробности]]:
 * `__call__()` → (`__prepare__()` для автоматического создания пространства имён, если есть), `__new__()`, `__init__()`
 *
 * определённые правила для [[py3ref:datamodel.html#object.__init__|__init__()]] и [[py3ref:datamodel.html#object.__new__|__new__()]]

<!> Не путать с наследованием!
[[py3ref:datamodel#metaclasses|Подробности]]:
 * (`__prepare__()` для автоматического создания пространства имён, если есть), `__new__()`, `__init__()`
  * можно перебить ещё `__call__` для внесения правок при создании экземпляра класса
 * `__new__()`
  * ''создаёт'' экземпляр объекта (а `__init__()` заполняет готовый)
  * это метод класса (такой `@classmethod` без декоратора)
  * в нём можно поменять всё, что в `__init__()` приезжает готовое и read-only: `__slots__`, имя класса (если это метакласс) и т. п.

<<Anchor(overall)>>
Общая картина:
 {{{#!python
class ctype(type):

    def __call__(self, *args, **kwargs):
        print("call", self, args, kwargs)
        return super().__call__(*args, **kwargs)

    def __new__(cls, name, parents, ns):
        print("new", cls, name, parents, ns)
        return super().__new__(cls, name, parents, ns)

    def __init__(self, name, parents, ns):
        print("init", self, parents, ns)
        return super().__init__(name, parents, ns)

class C(int, metaclass=ctype):
     pass

c = C("100500", base=16)
}}}
  →
 {{{
new <class '__main__.ctype'> C (<class 'int'>,) {'__module__': '__main__', '__qualname__': 'C'}
init <class '__main__.C'> (<class 'int'>,) {'__module__': '__main__', '__qualname__': 'C'}
call <class '__main__.C'> ('100500',) {'base': 16}
}}}
 * `__new__` — это метод класса: при вызове из `super()` поле `cls` надо передавать явно

Два примера:
 * Ненаследуемый класс
 {{{#!python
class final(type):
    def __new__(metacls, name, parents, namespace):
        for cls in parents:
            if isinstance(cls, final):
                raise TypeError(f"{cls.__name__} is final")
        return super(final, metacls).__new__(metacls, name, parents, namespace)
class E(metaclass=final): pass
class C: pass
class A(C, E): pass
}}}
 * Синглтон (больше синглтонов [[https://webdevblog.ru/realizaciya-shablona-singleton-v-python/|тут]])
 {{{#!python
class Singleton(type):
    _instance = None
    def __call__(cls, *args, **kw):
        if cls._instance is None:
             cls._instance = super(Singleton, cls).__call__(*args, **kw)
        return cls._instance

class S(metaclass=Singleton):
    A = 3
s, t = S(), S()
s.newfield = 100500
print(f"{s.newfield=}, {t.newfield=}")
print(f"{s is t=}")
}}}
 * Модуль [[py3doc:types]]

== Аннотации ==
Duck typing:
 * Экономия кода на описаниях и объявлениях типа
 * Экономия (несравненно бо́льшая) кода на всех этих ваших полиморфизмах
 * ⇒ Компактный читаемый код, хорошее отношение семантика/синтаксис
 * ⇒ Быстрое решение Д/З ☺
Однако:
 * Практически все ошибки — runtime
 * Много страданий от невнимательности (передал объект не того типа, и не заметил, пока не свалилось)
  * Вашей невнимательности не поможет даже хитрое IDE: оно ''тоже'' не знает о том, какого типа объекты правильные, какого — нет
  * (соответственно, о полях вашего объекта тоже)
 * Часть ''прагматики'' растворяется в коде (например, вы написали ''строковую'' функцию, как об этом узнать?)
 * Большие и сильно разрозненные проекты — ?

Поэтому нужны указания о типе полей классов, параметрах и возвращаемых значений функций/методов и т. п. — [[py3tut:controlflow#function-annotations|Аннотации]]
 * Пример аннотаций полей, параметров и возвращаемых значений
 {{{#!python
class C:
    A: int = 2
    def __init__(self, param: int = None, signed: bool = True):
        if param != None:
            self.A = param if signed else abs(param)

    def mult(self, mlt) -> str:
        return self.A * mlt

a, b = C(3), C("QWE")
print(f"{a.mult([2])=}, {b.mult(2)=}")
print(f"{a.__annotations__=}")
print(f"{a.mult.__annotations__=}")
print(f"{C.__init__.__annotations__}")
}}}
 * Аннотации сами по себе не влияют на семантику функции
 * Типы в аннотациях — это ''настоящие'' типы
 * Про аннотацию переменных [[https://dzone.com/articles/new-in-python-syntax-for-variable-annotations|на DZone]] ([[https://tproger.ru/translations/python-variable-annotations/|перевод на tproger]])

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

 Python 3.9 с нами :):: Просто прочитаем [[https://docs.python.org/3/whatsnew/3.9.html|What’s New In Python 3.9]]
  * [[https://docs.python.org/3/whatsnew/3.9.html#type-hinting-generics-in-standard-collections|в частности, дженерики]] ([[pep:pep-0585]])
   * Для указания, какого типа, например, ''элементы'' списка
   * `list[int]` в 3.9 vs. 3.8

Модуль [[py3doc:typing]]
 * Алиасы (практически `typedef`), `Any`, `NewType` (категоризация), `Callable`
 * Дженерики и [[py3doc:collections.abc]]
 * Инструменты: `NoReturn`, `Union`, `Optional`, `Type` (если сама переменная — класс), `Literal`, `Final`, …

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

 * [[https://habr.com/ru/company/lamoda/blog/432656/|Развесистая статья на Хабре]] (⩽ Python3.8, однако ☺, см [[pep:pep-0585]])
  * [[https://habr.com/ru/company/lamoda/blog/435988/|Её продолжение]]
 * [[py3doc:dataclass]] — типизированные структуры
  * [[https://habr.com/ru/post/415829/|тот же автор на Хабре]]

'''Важно''': в Python есть ''поддержка'' аннотаций, но практически нет их ''использования'' (разве что в [[py3doc:dataclasses]]). В язык не входит, делайте сами.
=== MyPy ===
Зачем аннотации?
 * Дисциплина программирования
  * большие, сверхбольшие и «долгие» проекты
 * ''Потенциально'' возможные проверки
 * Прагматика, включенная в синтаксис языка
 * Преобразование Python-кода в представления, требующие статической типизации
 * …

[[http://www.mypy-lang.org]]: ''статическая'' типизация в Python (ну, почти… или ''совсем''!)
 * --(Описание типов переменных, параметров и т. п.)---
 * Проверка выражений с типизированными данными
  * В т. .ч ''не''-проверка нетипизиварованных
 * Пример:
 {{{#!python
def fun(a: int, b) -> str:
    b *= a
    return b

def fun2(a: int) -> str:
    c: int = a + "1"
    return c

res: str
var: int
res = fun(1,"qwe")
res = fun(100, 200)
var = fun(1,2)
 }}}
 * Он запускается! Но проверку на статическую типизацию не проходит:
 {{{#!highlight console
$ mypy ex1.py
ex1.py:6: error: Unsupported operand types for + ("int" and "str")
ex1.py:7: error: Incompatible return value type (got "int", expected "str")
ex1.py:13: error: Incompatible types in assignment (expression has type "str", variable has type "int")
Found 3 errors in 1 file (checked 1 source file)
$ mypy --strict ex1.py
ex1.py:1: error: Function is missing a type annotation for one or more arguments
ex1.py:3: error: Returning Any from function declared to return "str"
ex1.py:6: error: Unsupported operand types for + ("int" and "str")
ex1.py:7: error: Incompatible return value type (got "int", expected "str")
ex1.py:13: error: Incompatible types in assignment (expression has type "str", variable has type "int")
Found 5 errors in 1 file (checked 1 source file)
}}}
 * ''Компиляция''. Если все объекты полностью типизированы, у них имеется эквивалент в виде соответствующих структур [[https://docs.python.org/3/c-api/index.html|PythonAPI]]. ЧСХ, у байт-кода __тоже__ есть эквивалент в Python API
 * Таинственный `mypyc`
  * Bleedng edge: [[https://github.com/mypyc/mypyc|было]], [[https://github.com/python/mypy/tree/master/mypyc|стало]]
 * Пока не рекомендуют использовать, но сами все свои модули им компилируют!
Пример для `mypyc`
{{{#!python
import time
from typing import Tuple

def fb(x:int,y:int)->Tuple[int,int]:
    return y,x+y

def test()->float:
    x:int=0
    y:int=1
    t:float=time.time()
    for i in range(1000000):
        x = 0
        y = 1
        for j in range(100):
            x,y=fb(x,y)
    return time.time()-t
}}}
Сравнение производительности:
{{{#!highlight console
$ mypyc speed.py
running build_ext
building 'speed' extension
creating build/temp.linux-x86_64-3.8
creating build/temp.linux-x86_64-3.8/build
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
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
$ mv speed.py speed_.py
$ ls
build ex1.py __pycache__ speed.cpython-38.so speed_.py
$ python3 -c "import speed_; print(speed_.test())"
12.719617366790771
$ python3 -c "import speed; print(speed.test())"
2.643144130706787
 }}}

== Д/З ==
Задача сложная, присутствует исследование документации!
 1.#0 Прочитать про:
  * Метаклассы
  * Модуль [[py3doc:inspect]] (вот тут исследование)
  * Аннотации
 1. `<<EJCMC(148, MyMypy, Типизация вручную)>>`
 Написать метакласс `checked`, при использовании которого в порождаемом классе проверяются соответствия типов всех аннотированных параметров и возвращаемых значений всех методов. В случае несовпадения инициируется `TypeError: Type mismatch: <параметр>`
  * Значения параметров по умолчанию не проверяются (для простоты)
  * Поля не проверяются, только методы
  * При ''вызове'' (по крайней мере, в тестах) не используются конструкции `*args` и `**kwargs` ( /!\ этот пункт я могу убрать, если выяснится, что оно работает и так, и так… но тогда оно будет работать и так, и так ☺)
  * Параметры проверяются (если они аннотированы, конечно) в следующем порядке
   1. Позиционные параметры, по порядку
   1. Явно заданные именные параметры в порядке вызова метода
   1. Позиционные параметры, полученные через `*args`. В этом случае в строке исключения пишется `args` (имя, которое при описании функции принимало запакованные позиционные параметры)
   1. Именные параметры, полученные через `**kwargs`
   1. Возвращаемое значение (имя параметра "`return`", как в аннотации)

{{{#!python
class E(metaclass=checked):
    def __init__(self, var: int):
        self.var = var if var%2 else str(var)

    def mix(self, val: int, opt) -> int:
        return self.var*val + opt

    def al(self, c: int, d:int=1, *e:int, f:int=1, **g:int):
        return self.var*d

e1, e2 = E(1), E(2)
code = """
e1.mix("q", "q")
e1.mix(2, 3)
e2.mix(2, "3")
e1.al("q")
e1.al(1, 2, 3, 4, 5, 6, foo=7, bar=8)
e2.al(1, 2, 3, 4, 5, 6, foo=7, bar=8)
e1.al("E", 2, 3, 4, 5, 6, foo=7, bar=8)
e1.al(1, "E", 3, 4, 5, 6, foo=7, bar=8)
e1.al(1, 2, "E", 4, 5, 6, foo=7, bar=8)
e1.al(1, 2, 3, "E", 5, 6, foo="7", bar=8)
e1.al(1, f="E", d=1)
e1.al(1, f=1, d="E")
e1.al(1, f="E", d="1")
e1.al(1, d="E", f="1")
e1.al(1, e="E")
e1.al(1, g="E")
"""

for c in code.strip().split("\n"):
    try:
        res = eval(c)
    except TypeError as E:
        res = E
    print(f"Run: {c}\nGot: {res}")
}}}
{{{
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
}}}

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

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

Метаклассы

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

Итак.

  • Класс можно создать просто функцией (aka Monkey patch)

  • Декоратором
  • От класса можно унаследоваться и всё модифицировать в потомке
    • TODO but why then?

  • Создание класса с помощью type(name, bases, dict)

       1 class C:
       2     pass
    
    • это вырожденный вызов type("имя", (кортеж родителей), {пространство имён})

       1 C = type("C", (), {})
    
  • Например,
       1 C = type('Simple', (), {'val': 42, 'getval': lambda self: self.val})
       2 c = C()
       3 c.val, c.getval()
    
  • Но type — это просто класс такой ⇒ от него можно унаследоваться, например, перебить ему __init__():

       1 class overtype(type):
       2     def __init__(self, Name, Parents, Dict):
       3         print(f" Class definition: {Name}{Parents}: {Dict}")
       4         super().__init__(Name, Parents, Dict)
       5 
       6 Boo = overtype("Boo", (), {"A": 100500})
       7 t = Boo()
       8 print(Boo, t, t.A)
    
  • а вот это Boo = overtype… можно записать так:

       1 
       2 class Boo(metaclass=overtype):
       3     A = 100500
    
  • (по сути, class C: — это class C(metaclass=type):)

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

  • (__prepare__() для автоматического создания пространства имён, если есть), __new__(), __init__()

    • можно перебить ещё __call__ для внесения правок при создании экземпляра класса

  • __new__()

    • создаёт экземпляр объекта (а __init__() заполняет готовый)

    • это метод класса (такой @classmethod без декоратора)

    • в нём можно поменять всё, что в __init__() приезжает готовое и read-only: __slots__, имя класса (если это метакласс) и т. п.

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

  •    1 class ctype(type):
       2 
       3     def __call__(self, *args, **kwargs):
       4         print("call", self, args, kwargs)
       5         return super().__call__(*args, **kwargs)
       6 
       7     def __new__(cls, name, parents, ns):
       8         print("new", cls, name, parents, ns)
       9         return super().__new__(cls, name, parents, ns)
      10 
      11     def __init__(self, name, parents, ns):
      12         print("init", self, parents, ns)
      13         return super().__init__(name, parents, ns)
      14 
      15 class C(int, metaclass=ctype):
      16      pass
      17 
      18 c = C("100500", base=16)
    
    new <class '__main__.ctype'> C (<class 'int'>,) {'__module__': '__main__', '__qualname__': 'C'}
    init <class '__main__.C'> (<class 'int'>,) {'__module__': '__main__', '__qualname__': 'C'}
    call <class '__main__.C'> ('100500',) {'base': 16}
  • __new__ — это метод класса: при вызове из super() поле cls надо передавать явно

Два примера:

  • Ненаследуемый класс
       1 class final(type):
       2     def __new__(metacls, name, parents, namespace):
       3         for cls in parents:
       4             if isinstance(cls, final):
       5                 raise TypeError(f"{cls.__name__} is final")
       6         return super(final, metacls).__new__(metacls, name, parents, namespace)
       7 class E(metaclass=final): pass
       8 class C: pass
       9 class A(C, E): pass
    
  • Синглтон (больше синглтонов тут)

       1 class Singleton(type):
       2     _instance = None
       3     def __call__(cls, *args, **kw):
       4         if cls._instance is None:
       5              cls._instance = super(Singleton, cls).__call__(*args, **kw)
       6         return cls._instance
       7 
       8 class S(metaclass=Singleton):
       9     A = 3
      10 s, t = S(), S()
      11 s.newfield = 100500
      12 print(f"{s.newfield=}, {t.newfield=}")
      13 print(f"{s is t=}")
    
  • Модуль types

Аннотации

Duck typing:

  • Экономия кода на описаниях и объявлениях типа
  • Экономия (несравненно бо́льшая) кода на всех этих ваших полиморфизмах
  • ⇒ Компактный читаемый код, хорошее отношение семантика/синтаксис
  • ⇒ Быстрое решение Д/З ☺

Однако:

  • Практически все ошибки — runtime
  • Много страданий от невнимательности (передал объект не того типа, и не заметил, пока не свалилось)
    • Вашей невнимательности не поможет даже хитрое IDE: оно тоже не знает о том, какого типа объекты правильные, какого — нет

    • (соответственно, о полях вашего объекта тоже)
  • Часть прагматики растворяется в коде (например, вы написали строковую функцию, как об этом узнать?)

  • Большие и сильно разрозненные проекты — ?

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

  • Пример аннотаций полей, параметров и возвращаемых значений
       1 class C:
       2     A: int = 2
       3     def __init__(self, param: int = None, signed: bool = True):
       4         if param != None:
       5             self.A = param if signed else abs(param)
       6 
       7     def mult(self, mlt) -> str:
       8         return self.A * mlt
       9 
      10 a, b = C(3), C("QWE")
      11 print(f"{a.mult([2])=}, {b.mult(2)=}")
      12 print(f"{a.__annotations__=}")
      13 print(f"{a.mult.__annotations__=}")
      14 print(f"{C.__init__.__annotations__}")
    
  • Аннотации сами по себе не влияют на семантику функции
  • Типы в аннотациях — это настоящие типы

  • Про аннотацию переменных на DZone (перевод на tproger)

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

Python 3.9 с нами :)

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

Модуль typing

  • Алиасы (практически typedef), Any, NewType (категоризация), Callable

  • Дженерики и collections.abc

  • Инструменты: NoReturn, Union, Optional, Type (если сама переменная — класс), Literal, Final, …

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

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

MyPy

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

  • Дисциплина программирования
    • большие, сверхбольшие и «долгие» проекты
  • Потенциально возможные проверки

  • Прагматика, включенная в синтаксис языка
  • Преобразование Python-кода в представления, требующие статической типизации

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

  • Описание типов переменных, параметров и т. п.-

  • Проверка выражений с типизированными данными
    • В т. .ч не-проверка нетипизиварованных

  • Пример:
       1 def fun(a: int, b) -> str:
       2     b *= a
       3     return b
       4 
       5 def fun2(a: int) -> str:
       6     c: int = a + "1"
       7     return c
       8 
       9 res: str
      10 var: int
      11 res = fun(1,"qwe")
      12 res = fun(100, 200)
      13 var = fun(1,2)
    
  • Он запускается! Но проверку на статическую типизацию не проходит:
       1 $ mypy ex1.py
       2 ex1.py:6: error: Unsupported operand types for + ("int" and "str")
       3 ex1.py:7: error: Incompatible return value type (got "int", expected "str")
       4 ex1.py:13: error: Incompatible types in assignment (expression has type "str", variable has type "int")
       5 Found 3 errors in 1 file (checked 1 source file)
       6 $ mypy --strict ex1.py
       7 ex1.py:1: error: Function is missing a type annotation for one or more arguments
       8 ex1.py:3: error: Returning Any from function declared to return "str"
       9 ex1.py:6: error: Unsupported operand types for + ("int" and "str")
      10 ex1.py:7: error: Incompatible return value type (got "int", expected "str")
      11 ex1.py:13: error: Incompatible types in assignment (expression has type "str", variable has type "int")
      12 Found 5 errors in 1 file (checked 1 source file)
      13 
    
  • Компиляция. Если все объекты полностью типизированы, у них имеется эквивалент в виде соответствующих структур PythonAPI. ЧСХ, у байт-кода тоже есть эквивалент в Python API

  • Таинственный mypyc

  • Пока не рекомендуют использовать, но сами все свои модули им компилируют!

Пример для 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. <<EJCMC(148, MyMypy, Типизация вручную)>> Написать метакласс checked, при использовании которого в порождаемом классе проверяются соответствия типов всех аннотированных параметров и возвращаемых значений всех методов. В случае несовпадения инициируется TypeError: Type mismatch: <параметр>

    • Значения параметров по умолчанию не проверяются (для простоты)
    • Поля не проверяются, только методы
    • При вызове (по крайней мере, в тестах) не используются конструкции *args и **kwargs ( /!\ этот пункт я могу убрать, если выяснится, что оно работает и так, и так… но тогда оно будет работать и так, и так ☺)

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

      4. Именные параметры, полученные через **kwargs

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

   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}")

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 (последним исправлял пользователь FrBrGeorge 2020-12-06 18:50:33)