Статический анализатор типов mypy предотвращает значительное количество возможных ошибок в коде на языке программирования Python. Главная его цель – предоставить возможность писать более надежный и легко поддерживаемый код.
Выше приведены простые примеры аннотаций типов функций. После имени аргумента через двоеточие указывается тип аргумента, а через стрелку после аргументов функции указывается тип возвращаемого значения.
Чтобы установить и протестировать mypy требуется сделать следующее:
Если же заменить тип возвращаемого значения greet на int, сделав программу некорректной, mypy выдаст следующую ошибку:
Стандартные типы
К стандартным скалярным (не коллекциям) типам относятся следующие:
Используя тип Union можно переписать в одну функцию следующим образом:
Вывод такой же, “Uryyb, jbeyq!”. Но, как видно в коде, нам приходится явно проверять условие, что возвращаемое значение для байтов имеет тип bytes, а не str, хотя иначе, согласно нашему алгоритму, быть не может.
Перегрузки
Проблему из предыдущего примера можно решить, используя перегрузки:
Для перегрузок мы не указываем реализацию, вместо нее требуется писать многоточие. В финальной реализации мы можем обращаться к функции, предполагая, что ее возможные типы соответствуют перегрузкам. Это позволяет более точно обозначить зависимости между типами аргументов и типом результата. Однако, в реализации все равно придется писать Union’ы.
Настройки
У mypy имеется некоторое количество опций, полный список которых можно посмотреть в официальной документации (
Опции могут быть включены через явный параметр (пример:
Пример mypy.ini файла:
Смысл опций из примера:
Кроме того, для интеграции mypy с существующими средствами разработки уже существуют готовые плагины. Для PyCharm и VisualStudio Code они так и называются, их легко найти в маркетплейсе. Mypy может быть легко интегрирован в CI/CD и использоваться при сборке проекта.
Используйте статическую типизацию всегда, когда возможно – это довольно легко упростит вам жизнь с современными технологиями.
Python:
def greet(name: str) -> str:
return f"Hello, {name}!"
def add_numbers(x: int, y: int) -> int:
return x + y
def get_length(s: str) -> int:
return len(s)
def is_even(n: int) -> bool:
return n % 2 == 0
Выше приведены простые примеры аннотаций типов функций. После имени аргумента через двоеточие указывается тип аргумента, а через стрелку после аргументов функции указывается тип возвращаемого значения.
Чтобы установить и протестировать mypy требуется сделать следующее:
- Устанавливаем mypy командой
pip install mypy
; - Сохраняем код в файл, для примера, в
mpy.py
; - Выполняем команду
mypy mpy.py
.
Success: no issues found in 1 source file
Если же заменить тип возвращаемого значения greet на int, сделав программу некорректной, mypy выдаст следующую ошибку:
Bash:
mpy.py:2: error: Incompatible return value type (got "str", expected "int") [return-value]
Found 1 error in 1 file (checked 1 source file)
Стандартные типы
К стандартным скалярным (не коллекциям) типам относятся следующие:
- int – целочисленная переменная любой длины. Например: 42, -10, 0;
- float – число с плавающей точкой. Например: 0.13, 5.0, math.pi;
- bool – булево значение, True или False;
- str – строка. Например: “Hello, world!”;
- bytes – последовательность байт. Например: b”Hello”;
- bytearray – изменяемый массив байт. Например: bytearray(b”Hello”);
- None – аналог void типа в Си. Имеет None как единственное значение.
- List[T] – список, элементы которого имеют тип T;
- Tuple[T1, T2, …, TN] – кортеж, первый элемент которого имеет тип T1, второго – T2 и так далее;
- Dict[K, V] – словарь, ключи которого имеют тип K, а значения – тип V;
- Optional[T, V] – тип T или None, аналогично Union[T, None];
- Any – любой тип;
- Union[T1, T2, …, TN] – любой тип из T1, T2, …, TN;
- Callable[[T1, …], TResult] – функция, которая принимает аргументы типов T1, … и возвращает TResult.
Python:
from typing import List
def rot13(b: bytes) -> bytes:
result: List[int] = []
for byte in b:
if 65 <= byte <= 90:
result.append((byte - 65 + 13) % 26 + 65)
elif 97 <= byte <= 122:
result.append((byte - 97 + 13) % 26 + 97)
else:
result.append(byte)
return bytes(result)
def crypt(s: str) -> str:
b: bytes = s.encode('ascii')
roted: bytes = rot13(b)
return roted.decode('ascii')
print(crypt("Hello, world!"))
Используя тип Union можно переписать в одну функцию следующим образом:
Python:
from typing import List, Union
def rot13(data: Union[bytes, str]) -> Union[bytes, str]:
if isinstance(data, str):
roted = rot13(data.encode('ascii'))
if isinstance(roted, bytes):
return roted.decode('ascii')
else:
return roted
else:
result: List[int] = []
for byte in data:
if 65 <= byte <= 90:
result.append((byte - 65 + 13) % 26 + 65)
elif 97 <= byte <= 122:
result.append((byte - 97 + 13) % 26 + 97)
else:
result.append(byte)
return bytes(result)
print(rot13("Hello, world!"))
Вывод такой же, “Uryyb, jbeyq!”. Но, как видно в коде, нам приходится явно проверять условие, что возвращаемое значение для байтов имеет тип bytes, а не str, хотя иначе, согласно нашему алгоритму, быть не может.
Перегрузки
Проблему из предыдущего примера можно решить, используя перегрузки:
Python:
from typing import List, Union, overload
@overload
def rot13(data: bytes) -> bytes: ...
@overload
def rot13(data: str) -> str: ...
def rot13(data: Union[bytes, str]) -> Union[bytes, str]:
if isinstance(data, str):
return rot13(data.encode('ascii')).decode('ascii')
else:
result: List[int] = []
for byte in data:
if 65 <= byte <= 90:
result.append((byte - 65 + 13) % 26 + 65)
elif 97 <= byte <= 122:
result.append((byte - 97 + 13) % 26 + 97)
else:
result.append(byte)
return bytes(result)
print(rot13("Hello, world!"))
Для перегрузок мы не указываем реализацию, вместо нее требуется писать многоточие. В финальной реализации мы можем обращаться к функции, предполагая, что ее возможные типы соответствуют перегрузкам. Это позволяет более точно обозначить зависимости между типами аргументов и типом результата. Однако, в реализации все равно придется писать Union’ы.
Настройки
У mypy имеется некоторое количество опций, полный список которых можно посмотреть в официальной документации (
Ссылка скрыта от гостей
). Они могут быть полезны при разработке, так как позволяют задать "строгость" проверки.Опции могут быть включены через явный параметр (пример:
--strict
), но удобнее содержать их в файлах. Mypy поддерживает следующие файлы конфигурации: mypy.ini
, .mypy.ini
, pyproject.toml
и setup.cfg
. Изначально он будет ожидать один из этих файлов в текущей директории.Пример mypy.ini файла:
Код:
[mypy]
disallow_untyped_defs=True
no_implicit_optional=True
warn_return_any=True
Смысл опций из примера:
- disallow_untyped_defs – запрещает объявлять функции без аннотации типов;
- no_implicit_optional – без этой опции аргументы функции могут иметь тип None в качестве параметра по-умолчанию. С этой опцией такое неявное поведение запрещено, а гарантии типов сохранены;
- warn_return_any – название говорит само за себя. С этой опцией mypy предупреждает, если в качестве возвращаемого типа было указано Any. Чаще всего Any ставится как временная заглушка, так что mypy поможет вам не забыть поменять заглушку на действительный тип.
- check_untyped_defs – проверяет корректность типизации в функции без аннотаций. По-умолчанию mypy закрывает глаза на все, что происходит внутри таких функций. С этой опцией он проверяет корректность типизации всех операций и вызовов внутри;
- strict – включает все опциональные дополнительные проверки;
- warn_unreachable – сообщает, если какой-то кусок кода никогда не выполнается. Если так случилось, вероятно была допущена ошибка в проверке условий.
Заключение
Mypy очень прост в изучение и использовании. Несмотря на простоту, он позволяет явным образом прописать используемые в проекте типы, предотвращая множество ошибок. Если вы случайно передали в функцию не тот аргумент, без mypy есть риск узнать об этом уже на стадии выполнения, возможно даже в продакшене.Кроме того, для интеграции mypy с существующими средствами разработки уже существуют готовые плагины. Для PyCharm и VisualStudio Code они так и называются, их легко найти в маркетплейсе. Mypy может быть легко интегрирован в CI/CD и использоваться при сборке проекта.
Используйте статическую типизацию всегда, когда возможно – это довольно легко упростит вам жизнь с современными технологиями.
Последнее редактирование модератором: