Type Hints Python
Введение | |
Простой пример | |
Более сложные условия | |
typing | |
Стандартная библиотека | |
Объявление переменных | |
weakref | |
Похожие статьи |
Введение
В соответствии с
PEP 484
рекомендуется указывать ожидаемые типы данных как для параметров, так и для возвращаемого значения.
Официальный термин, который используется в англоязычной среде это Optional Type Annotations
Никаких ограничений в самом Python эти подсказки не накладывают. Можно спокойно выполнить код в котором типы не соответствуют указанным в подсказках.
Подсказки о типах полезны если вы планируете проверять ваш код одним из существующих линтеров.
Основные линтеры для Python кода это
- mypy от DropBox
- pytype от Google
- Pyright / Pylance от MicroSoft
- Pyre от Meta
Функции с подсказками о типах выглядят следующим оригинальным образом
def my_fun(s: str, factor: int) -> int: return s * factor
В подсказках типов можно использовать любые существующие типы данных, в том числе созданные пользователем. Пример с bool
def odd(n: int) -> bool: return n % 2 != 0
Пример с пользовательским типом
class MyClass: def __init__(self, value: float) -> None: self.value = value def get_value(obj: MyClass) -> bool: return int(obj.value) % 2 != 0
Значимость аннотации типов для Python сообщества значительно выросло с момента написания PEP 484. Успех Pydantic является одним из подтверждений спроса на проверку типов. В связи с этим был создан совет по типам - Typing Council, о котором можно прочитать в PEP 729
В комментариях
Самым первым способом создавать подсказки были комментарии. Никогда не видел такого в рабочих Python 3 проектах, видимо это был способ для Python 2
def square(x): # type: (float) -> float return x * x
Простой пример
В Python 3 аннотации типов создаются следующим оригинальным образом
# typehints.py def sum(first: float, second: float) -> float: return(first + second) print(sum(3.0, 4.6))
python typehints.py
7.6
Так как никаких ограничений в самом Python эти подсказки не накладывают. Можно спокойно выполнить
print(sum("a", "b"))
И получить
ab
Подсказки нужны скорее для сторонней проверки, каким-либо инструментом, например mypy.
python -m mypy ./typehints.py
typehints.py:7: error: Argument 1 to "sum" has incompatible type "str"; expected "float" [arg-type] typehints.py:7: error: Argument 2 to "sum" has incompatible type "str"; expected "float" [arg-type] Found 2 errors in 1 file (checked 1 source file)
Более сложные условия
Часто бывает полезно сделать подсказку на тип данных более сложным чем встроенный.
Например объект должен быть списком из целых чисел,
а не любым списком. В версиях Python 3.5, 3.6, 3.7 и 3.8 использовали модуль typing, который появился в Python 3.5.
С версии 3.9 возможно создавать составные условия из стандартных типов без использования typing
typing
from typing import List def printer(ls: List[int]) -> None: for elem in ls: print(elem) printer([1, 2])
В typing List пишется с большой буквы
python -m mypy .\typehints.py
Success: no issues found in 1 source file
Если теперь попробовать передать в функцию список со строкой mypy выдаст ошибку
… printer([1, "a"])
python -m mypy .\typehints.py
typehints.py:8: error: List item 1 has incompatible type "str"; expected "int" [list-item] Found 1 error in 1 file (checked 1 source file)
Начиная с Python 3.9 этот способ указания стандартных типов является устаревшим, но всё ещё используется.
Не советую писать код таким способом в виду потенциальной
потери поддержки в новых версиях Python
Насколько я понимаю использовать другие функции typing всё ещё можно но следует следить за тем что получает статус deprecated в
документации
С помощью typing можно создавать аннотации и для более сложных типов. Например, нужен список, элементы которого могут быть как int так и str, но не float
from typing import List, Union list_elem = Union[int, str] def printer(ls: List[list_elem]) -> None: for elem in ls: print(elem) printer([1, 2.0])
python -m mypy typehints.py
typehints.py:11: error: List item 1 has incompatible type "float"; expected "int | str" [list-item] Found 1 error in 1 file (checked 1 source file)
float не подходит, а вот если заменить 2.0 на строку "abc" ошибки не будет
… printer([1, "abc"])
python -m mypy typehints.py
Success: no issues found in 1 source file
Можно заранее создать кастомный тип my_list в котором разрешены целые числа и строки.
from typing import List, Union my_list = List[Union[int, str]] def printer(ls: my_list) -> None: for elem in ls: print(elem) printer([1, "abc"])
Для необязательных параметров в typing предусмотрено ключевое слово Optional
from typing import Optional def sum(a: int, b: int, c: Optional[int] = 100) -> int: if c: return a + b + c else: return a + b print(sum(1, 2)) print(sum(1, 2, 3))
python typehints.py
103 6
python -m mypy typehints.py
Success: no issues found in 1 source file
Можно создавать аннотации для словарей указывая разрешенные типы как ключей так и значений.
from typing import Dict, Union my_dict = Dict[str, Union[int, str]] def printer(dct: my_dict) -> None: for k, v in dct.items(): print(k, v) printer({"k": "v", "two": 2})
k v two 2
python -m mypy typehints.py
Success: no issues found in 1 source file
… printer({"k": "v", "two": 2})
k v two 2.0
python -m mypy typehints.py
typehints.py:12: error: Dict entry 1 has incompatible type "str": "float"; expected "str": "int | str" [dict-item] Found 1 error in 1 file (checked 1 source file)
Стандартная библиотека
Начиная с Python 3.9 для составных подсказок можно использовать встроенные коллекции , например, обычный список
def printer(ls: list[int]) -> None: for elem in ls: print(elem) printer([1, 2])
python typehints.py
1 2
Если выполнив этот код через mypy вы получаете ошибку
python -m mypy .\typehints.py
typehints.py:1: error: "list" is not subscriptable, use "typing.List" instead [misc] Found 1 error in 1 file (checked 1 source file)
Значит у вас версия Python, в которой mypy не поддерживает обычные типы данных в составных подсказках - в PEP 585 support is broken. #9873 можно прочитать о задержке с поддержкой этой фичи. В 3.12 поддержка точно есть.
Часто бывает нужна аннотация на ещё более сложный тип. Например, нужен список, элементами которого могут быть как целые числа, так и
словари.
Начиная с Python 3.10 можно воспользоваться оператором |.
PEP 604
Убедимся, что без | mypy видит ошибку
def printer(ls: list[int]) -> None: for elem in ls: print(elem) printer([1, {"k": "v"}])
python -m mypy typehints.py
typehints.py:5: error: List item 1 has incompatible type "dict[str, str]"; expected "int" [list-item] Found 1 error in 1 file (checked 1 source file)
Добавим |
def printer(ls: list[int | dict]) -> None: for elem in ls: print(elem) printer([1, {"k": "v"}])
python -m mypy typehints.py
Success: no issues found in 1 source file
Можно пойти дальше и указать, что словарь должен быть вида строка:строка. Теперь если передать словарь вида str:int mypy выдаст ошибку.
def printer(ls: list[int | dict[str, str]]) -> None: for elem in ls: print(elem) printer([1, {"k": 3}])
python -m mypy .\typehints.py
typehints.py:5: error: Dict entry 0 has incompatible type "str": "int"; expected "str": "str" [dict-item] Found 1 error in 1 file (checked 1 source file)
В фреймворке
FastAPI
указание или не указание type hints позволяет
накладывать реальные ограничения
на передаваемые агрументы.
Type hints используются не только в функциях но и, например при объявлении
dataclass
Переменные
Начиная с Python 3.6 можно создавать аннотации типов при объявлении переменных
x: int = 3 print(x * x)
python -m mypy .\typehints.py
Success: no issues found in 1 source file
Неверная аннотация будет проигнорирована интерпретатором, но замечена mypy
s: str = 1
python -m mypy .\typehints.py
typehints.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment] Found 1 error in 1 file (checked 1 source file)
mypy может понять, что тип используется неправильно. В данном примере можно и не использовать аннотацию, так как mypy поймёт его и так.
s: str = "abc" print(s * s)
python -m mypy .\typehints.py
typehints.py:2: error: Unsupported operand types for * ("str" and "str") [operator] Found 1 error in 1 file (checked 1 source file)
weakref
Если нужно сделать аннотацию типа для слабой ссылки нужно воспользоваться weakref.ReferenceType
self.data: weakref.ReferenceType["MyType"] = weakref.ref(data)
Optional
Рассмотрим функцию, которая возвращает либо строку либо None
def parity(value: int | str) -> str | None: try: value = int(value) except ValueError as e: print(e) return None else: if value % 2 == 0: return "even" else: return "odd" arg = "2" s: str = f"Parity of {arg} is " + parity(arg) print(s)
Parity of 2 is even
Если попытаться использовать возвращаемое значение из этой функции как строку - mypy выдаст предупреждение
src\t.py:14: error: Unsupported operand types for + ("str" and "None") [operator] src\t.py:14: note: Right operand is of type "str | None" Found 1 error in 1 file (checked 1 source file)
Потому что в случае нековертируемого аргумента строку придётся складывать с None и это приведёт к ошибке
… arg = "A" s: str = f"{arg} is " + parity(arg) print(s)
Traceback (most recent call last): File "C:\Users\Andrei\src\t.py", line 14, in <module> s: str = f"{arg} is " + parity(arg) ~~~~~~~~~~~~~^~~~~~~~~~~~~ TypeError: can only concatenate str (not "NoneType") to str
В данном примере можно изменить саму функцию, чтобы она и при пойманном исключении возвращала строку, например, "unknown" но мы исходим
из того, что менять код функции нельзя.
Исправить ситуацию без редактирования функции можно приведением результата к типу str
… arg = "A" s: str = f"Parity of {arg} is " + str(parity(arg)) print(s)
invalid literal for int() with base 10: 'A' Parity of A is None
python -m mypy t.py
Success: no issues found in 1 source file
Либо можно воспользоваться дополнительной проверкой на тип
… arg = "A" p: str = str(parity(arg)) if parity(arg) else "undefined" s: str = f"Parity of {arg} is " + p print(s)
К сожалению, чтобы mypy не ругался здесь тоже присутствует совершенно не нужное приведение к типу str
invalid literal for int() with base 10: 'A' Parity of A is undefined
python -m mypy t.py
Success: no issues found in 1 source file
Автор статьи: Андрей Олегович
Основы Python | |
Type Hints | |
__future__ | |
configparser | |
Менеджер контекста | |
docstring | |
#!: Shebang | |
Объекты | |
Итерация | |
os | |
pathlib |
РЕКЛАМА от Яндекса. Может быть недоступна в вашем регионе
Конец рекламы. Если там пусто считайте это рекламой моей телеги