Как работать с типизацией в Python
Разбор основ типизации кода в Python и её роли в динамически-типизированном языке, который будет наиболее полезен новичкам в Python.
73К открытий78К показов
Рассказывает команда SimbirSoft
Первые упоминания о подсказках типов в языке программирования Python появились в базе Python Enhancement Proposals (PEP-483). Такие подсказки нужны для улучшения статического анализа кода и автодополнения редакторами, что помогает снизить риски появления багов в коде.
В этой статье мы рассмотрим основы типизации кода Python и ее роль в динамически-типизированном языке, эта информация будет наиболее полезна для начинающих Python-разработчиков.
Типизация в Python
Для обозначения базовых типов переменных используются сами типы:
str
int
float
bool
complex
bytes
- etc.
Пример использования базовых типов в python-функции:
Помимо этого, можно параметризировать более сложные типы, например, List
. Такие типы могут принимать значения параметров, которые помогают более точно описать тип функции. Так, например, List[int]
указывает на то, что список состоит только из целочисленных значений.
Пример кода:
Кроме List
, существуют и другие типы из модуля typing, которые можно параметризировать. Такие типы называются Generic-типами. Такого рода типа определены для многих встроенных в Python структур данных:
Set[x]
FrozenSet[x]
ByteString[x]
Dict[x, y]
DefaultDict[x, y]
OrderedDict[x, y]
ChainMap[x,y]
Counter[x, int]
Deque[x]
- и т.д.
Как можно заметить, некоторые типы имеют несколько параметров, которые можно описать. Например, Dict[x, y]
означает, что это будет словарь, где ключи будут иметь тип x
, а значения – тип y
.
Также есть более абстрактные типы, например:
Mapping[x, y]
– объект имеет реализации метода__getitem__
;Iterable[x]
– объект имеет реализацию метода__iter__
.
При этом функции тоже имеют свои типы. Например, для описания функции можно использовать тип Callable
, где указываются типы входных параметров и возвращаемых значений. Пример использования:
Тип Callable
:
- говорит о том, что у объекта реализован метод
__call__
; - описывает типы параметров к этому методу.
На первом месте стоит массив типов входных параметров, на втором — тип возвращаемого значения.
Про остальные абстрактные типы контейнеров можно прочитать в документации Python.
Также есть более конкретные типы, например Literal[x]
, где x
указывает не тип, а конкретное значение. Например Literal[3]
означает цифру 3. Используют такой тип крайне редко.
Также Python позволяет определять свои Generic-типы.
В данном примере TypeVar
означает переменную любого типа, которую можно подставить при указании. Например:
Для определения собственных типов наследование возможно не только от Generic
, но и от других абстрактных типов, например, таких, как Mapping
, Iterable
.
На месте KeyType
или ValueType
могут быть конкретные типы.
Также есть специальные конструкции, которые позволяют комбинировать типы. Например, Union[x, y, ...]
— один из типов. Если переменной может быть как int
, так и float
, то как тип следует указать Union[int, float]
. Если переменной может быть как int
, так и None
, то в качестве типа можно указать Union[int,None]
или, что предпочтительно, Optional[int]
.
Зачем это нужно
Цель — указать разработчику на ожидаемый тип данных при получении или возврате данных из функции или метода. В свою очередь, это позволяет сократить количество багов, ускорить написание кода и улучшить его качество.
Допустим, у вас есть класс юзера и функция, которая преобразует json в User
.
Конечно, можно написать и проще:
Однако, в обоих случаях может возникнуть ошибка, если ключ age
будет присутствовать и при этом иметь строковый тип. Валидация типов добавляет не очень много строк кода, но при большом количестве моделей может занимать немало места в проекте.
Использование Pydantic помогает корректно валидировать данные, при этом тип автоматически поменяется на требуемый.
Как можно заметить, более строгая типизация кода помогает сделать его проще и безопаснее. Однако, использование некоторых возможностей Pydantic может нежелательно повлиять на код. Так, мутация данных при валидации способна привести к тому, что тип значения модели будет непонятен. Например:
В данном примере созданный User после валидации будет иметь отличный от того, который был указан в модели. Это ведет к возможным крупным багам, которые лучше всегда избегать.
Также сейчас набирает большую популярность фреймворк FastAPI, который, благодаря Pydantic, позволяет быстро писать веб-приложения с автоматической валидацией данных.
В данном примере эндпоинт /item автоматически валидирует входящий json и передает его в функцию как требуемую модель.
Также для уменьшения количества багов используют mypy, который позволяет проводить статический анализ кода на соответствие типов. За счет этого зачастую можно избежать очевидных багов или несоответствий типов в функциях.
И как бонус для тех, кто ленится вручную поддерживать типизацию. MonkeyType дает возможность автоматически проставить типы во всех функциях, хотя после запуска этой программы обычно требуется пройтись по коду и поправить некоторые значения, которые оказались распознаны не так, как предполагалось.
Нововведения Python 3.9.0
Начиная с недавно вышедшей версии Python 3.9, у разработчиков больше нет необходимости импортировать абстрактные коллекции для описания типов. Теперь вместо typing.Dict[x, y]
можно использовать dict[x,y]
, то же самое происходит с Deque
, List
, Counter
и т.д. Полное описание этого нововведения можно прочитать тут: PEP-585.
Также добавили аннотации типов, которые в дальнейшем могут быть использованы инструментами статического анализа. variable: Annotated[T, x]
где T
— тип переменной variable
, а x
— некоторые метаданные для переменной. По оценкам некоторых авторов, эти метаданные могут быть использованы также и во время выполнения (подробности смотрите в PEP-593).
Заключение
В этой статье мы рассмотрели некоторые типы в языке Python. В заключение отметим, что типизированный код в Python становится намного более читаемым и очевидным, что помогает проводить ревью в команде и не допускать глупых ошибок. Хорошее описание типов также позволяет разработчикам быстрее влиться в проект, понять, что происходит, и погрузиться в задачи. Также при использовании определенных библиотек удается в несколько раз сократить количество строк кода, которые ранее требовались только для валидации типов и значений.
73К открытий78К показов