- PVSM.RU - https://www.pvsm.ru -
Добрый день, друзья. А мы продолжаем наращивать интенсивность запуска новых курсов и уже сейчас рады сообщить о том, что в конце апреля стартуют занятия по курсу «Web-разработчик на Python» [1]. В связи с этим традиционно делимся переводом полезного материала. Начнём.
Известно, что Python – язык с динамической типизацией. Очень просто писать DSL-подобные фреймворки, которые трудно разобрать инструментами статичной проверки типа. Несмотря на это, с помощью последних функциональных новшеств mypy, таких как protocols [2] и literal types [3], а также с базовой поддержкой метаклассов и поддержкой дескриптора, мы можем чаще получать точные типы, однако по прежнему трудно избежать ложных срабатываний и других негативных факторов. Чтобы решить эту проблему и избежать необходимости кастомизировать систему типов для каждого фреймворка, mypy поддерживает систему плагинов [4]. Плагины — это модули в Python, которые обеспечивают обратные вызовы (plugin hooks), которые mypy вызовет при проверке типов классов и функций, взаимодействующих с библиотекой или фреймворком. Таким образом можно точнее выделить тип возвращаемой функции, который в противном случае выразить крайне трудно, либо автоматически сгенерировать некоторые методы класса, чтобы отразить эффекты декоратора. Чтобы узнать больше об архитектуре системы плагинов и увидеть полный список возможностей, ознакомьтесь с документацией [4].

Связанные плагины для стандартной библиотеки
Mypy поставляется с плагинами по умолчанию для реализации базовых функций и классов, а также с модулями ctypes, contextlib и dataclasses. Он также включает в себя плагины для attrs (так исторически сложилось, что это первый сторонний плагин написанный для mypy). Эти плагины позволяют mypy точнее определять типы и правильно проверять код на тип с помощью этих функций библиотеки. Чтобы показать это на примере, взгляните на отрывок кода:
from dataclasses import dataclass
from typing import Generic, TypeVar
@dataclass
class TaggedVector(Generic[T]):
data: List[T]
tag: str
position = TaggedVector([0, 0, 0], 'origin')
Выше, get_class_decorator_hook() вызывается при определении класса. Это добавляет автосоздаваемые методы, включая __init__(), в тело функции. Mypy использует такой конструктор, чтобы правильно вычислить TaggedVector[int] в качестве типа для position. Как видно из примера, плагины работают даже с обобщенными (generic) классами.
Вот еще один фрагмент кода:
from contextlib import contextmanager
@contextmanager
def timer(title: str) -> Iterator[float]:
...
with timer(9000) as tm:
...
Здесь get_function_hook() обеспечивает точный возвращаемый тип для декоратора contextmanager, таким образом вызовы декорированной функции могут быть проверены на соответствие определенному типу. Теперь mypy может распознать ошибку: аргумент для timer() должен быть строкой.
Комбинирование плагинов и заглушек
Помимо использований динамических функций Python, фреймворки часто сталкиваются с проблемой наличия больших API. Mypy нужны файлы заглушки [5] для библиотек для проверки кода, который использует эти библиотеки (только если библиотека не содержит встроенных аннотаций, что встречается не так часто). Распространение заглушек для больших фреймворков с помощью typeshed [6] не является общей практикой:
Пакеты заглушек (Stub packages), представленные в PEP 561 [7], позволяют делать следующее:
Более того, pip позволяет комбинировать различные заглушки для библиотек и соответствующих плагинов mypy в один дистрибутив. Заглушки для фреймворка или соответствующего плагина mypy могут быть легко разработаны и скомпонованы вместе в один дистрибутив, что крайне полезно, поскольку плагины заполняют отсутствующие или неточные определения в заглушках.
Последний пример такого пакета это SQLAlchemy stubs and plugin [8], с первым публичным релизом версии 0.1, который был некоторое время назад опубликован на PyPI. Несмотря на то, что этот проект находится в ранней Alpha версии, мы уже спокойно можем использовать его в DropBox для улучшения проверки типов. Плагин понимает базовые декларации ORM:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
Во фрагменте кода выше, плагин использует get_dynamic_class_hook(), чтобы сообщить mypy, что Base является допустимым базовым классом, даже если он таковым не выглядит. Затем get_base_class_hook() вызывается для определения User, и добавляет несколько автоматически сгенерированных атрибутов. Дальше мы создаем экземпляр модели:
user = User(id=42, name=42)
Вызвана get_function_hook(), поэтому mypy может указать на ошибку: получено значение типа integer вместо имени пользователя.
Заглушки определяют Column в качестве generic деcкриптора, так, чтобы атрибуты модели получили правильные типы:
id_col = User.id # Inferred type is "Column[int]"
name = user.name # Inferred type is "Optional[str]"
Мы приветствуем PR, которые добавляют заглушкам более точные типы (прогресс для основных модулей отслеживается здесь [9]).
Вот несколько подводных камней, которые мы обнаружили во время работы над заглушками:
__getattr__(), чтобы избежать ложных срабатываний на ранних стадиях, когда заглушки не завершены (это пресекает ошибки mypy, в случае отсутствия атрибутов модуля). Вы также можете использовать это в файлах __init__.py, если отсутствуют какие-либо подмодули.typing.Generic.)Недавно выпущенные плагины mypy
Уже сейчас есть несколько доступных плагинов для популярных фреймворков Python. Отдельно от упомянутого выше плагина для SQLAlchemy [8], другие примечательные примеры пакетов с заглушками и встроенным плагином mypy включают заглушки для интерфейсов Django [12] и Zope [13]. Сейчас над этими проектами ведется активная работа.
Установка и подключение пакетов заглушек и плагинов
Используйте pip, чтобы установить пакет с плагином для mypy и/или заглушки в виртуальную среду, где уже стоит mypy:
$ pip install sqlalchemy-stubs
Mypy автоматически обнаружит установленные заглушки. Чтобы подключить установленные плагины, включите их непосредственно в mypy.ini (или в пользовательский файл конфигурации):
[mypy]
plugins = sqlmypy, mypy_django_plugin.main
Разработка плагинов mypy и написание заглушек
Если вы хотите разработать пакет заглушек и плагинов для фреймворка, который вы используете, мы можете использовать репозиторий sqlalchemy-stubs [8] в качестве шаблона. Он включает в себя файл setup.py, тестирование инфраструктуры с использованием тестов, управляемых данными, и пример класса плагина с набором хуков для плагина (plugin hooks). Мы рекомендуем использовать stubgen [14] для автоматической генерации заглушек, которые поставляются с mypy, чтобы начать их использовать. Stubgen несколько улучшился в mypy 0.670.
Изучите документацию [4], если вы хотите узнать больше о системе плагинов mypy. Также вы можете поискать в интернете исходные коды плагинов, о которых говорилось в статье. Если у вас есть вопросы, вы можете задать их здесь [15].
15 апреля пройдет бесплатный открытый вебинар [16] по курсу, который проведет один из организаторов сообщества Moscow Python — Владимир Филонов [17], записывайтесь, будет интересно. А сейчас мы ждём ваши комментарии по переведенному материалу.
Автор: Дмитрий
Источник [18]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/314149
Ссылки в тексте:
[1] «Web-разработчик на Python»: https://otus.pw/ZHs7/
[2] protocols: https://mypy.readthedocs.io/en/latest/protocols.html
[3] literal types: https://mypy.readthedocs.io/en/latest/literal_types.html
[4] плагинов: https://mypy.readthedocs.io/en/latest/extending_mypy.html#extending-mypy-using-plugins
[5] заглушки: https://www.python.org/dev/peps/pep-0484/#stub-files
[6] typeshed: https://github.com/python/typeshed
[7] PEP 561: https://www.python.org/dev/peps/pep-0561/
[8] SQLAlchemy stubs and plugin: https://github.com/dropbox/sqlalchemy-stubs
[9] здесь: https://github.com/dropbox/sqlalchemy-stubs/issues/3
[10] Дескрипторы: https://docs.python.org/3/howto/descriptor.html
[11] обойти: https://mypy.readthedocs.io/en/latest/common_issues.html#using-classes-that-are-generic-in-stubs-but-not-at-runtime
[12] Django: https://github.com/mkurnikov/django-stubs
[13] Zope: https://github.com/Shoobx/mypy-zope
[14] stubgen: https://mypy.readthedocs.io/en/latest/stubgen.html
[15] здесь: https://gitter.im/python/typing
[16] открытый вебинар: https://otus.pw/hxiP/
[17] Владимир Филонов: https://otus.pw/PMjY/
[18] Источник: https://habr.com/ru/post/447556/?utm_source=habrahabr&utm_medium=rss&utm_campaign=447556
Нажмите здесь для печати.