Модуль typing и аннотация типов в Python
Development, Python | Комментировать запись
Модуль typing, который добавлен в Python 3.5, предоставляет способ указания типов, что позволяет программам проверки статических типов и линтерам точно предсказывать ошибки.
Иногда разработчикам бывает сложно выяснить, что именно происходит в коде из-за того, что Python должен определять тип объектов во время выполнения.
Внешние средства проверки типов, например PyCharm IDE, не дают нужных результатов. Он правильно прогнозирует ошибки всего в 50% случаев, согласно доступной статистике.
Python решает эту проблему, вводя так называемуые подсказки типов (также известную как аннотация типов) – так он помогает внешним средствам проверки типов находить ошибки. Так разработчики могут указать тип используемого объекта (объектов) во время самой компиляции и убедиться, что средства проверки типов работают правильно.
Код Python становится читабельным и понятным для других пользователей!
Примечание: Фактически проверка типов во время компиляции не выполняется. Ошибки компиляции не будет, если фактический возвращенный объект не был того же типа, что и подсказка. Поэтому для выявления любых ошибок типов мы используем внешние средства проверки типов, к примеру mypy.
Требования
Для эффективного использования модуля typing и тестирования статического соответствия типов рекомендуется использовать программную проверку типов – линтер. Mypy одна из наиболее широко используемых программ для проверки, поэтому мы советуем вам установить ее прямо сейчас.
Для этого используйте команду:
pip3 install mypy
Вы можете запустить mypy для любого файла Python, чтобы проверить соответствие типов. Это делается так:
mypy program.py
После устранения ошибок попробуйте запустить программу в обычном режиме, используя:
python program.py
Теперь давайте попробуем использовать некоторые функции модуля.
Подсказки типа, или типовая аннотация
Типовая аннотация в функциях
Мы можем аннотировать функцию, чтобы указать ее возвращаемый тип и типы ее параметров.
def print_list(a: list) -> None: print(a)
Этот код сообщает средству проверки типов (у нас это mypy), что у нас есть функция print_list(), которая принимает list в качестве аргумента и возвращает None.
def print_list(a: list) -> None: print(a) print_list([1, 2, 3]) print_list(1)
Сначала давайте запустим это в mypy:
vijay@JournalDev:~ $ mypy printlist.py printlist.py:5: error: Argument 1 to "print_list" has incompatible type "int"; expected "List[Any]" Found 1 error in 1 file (checked 1 source file)
Поскольку строка № 5 имеет аргумент int, а не list, мы получаем ошибку.
Аннотация типов в переменных
Начиная с Python 3.6, мы также можем аннотировать типы переменных. Но если вы хотите, чтобы тип переменной изменился до возврата из функции, то делать это не обязательно.
# Annotates 'radius' to be a float radius: float = 1.5 # We can annotate a variable without assigning a value! sample: int # Annotates 'area' to return a float def area(r: float) -> float: return 3.1415 * r * r print(area(radius)) # Print all annotations of the function using # the '__annotations__' dictionary print('Dictionary of Annotations for area():', area.__annotations__)
Вывод mypy:
vijay@JournalDev: ~ $ mypy find_area.py && python find_area.py Success: no issues found in 1 source file 7.068375 Dictionary of Annotations for area(): {'r': <class 'float'>, 'return': <class 'float'>}
Это рекомендуемый способ использования mypy: сначала предоставляем аннотации типов, а уже потом применяем средство проверки типов.
Псевдонимы типов
Модуль typing предоставляет нам псевдонимы типов. Чтобы определить такой псевдоним, присвойте его типу.
from typing import List # Vector is a list of float values Vector = List[float] def scale(scalar: float, vector: Vector) -> Vector: return [scalar * num for num in vector]
В результате получится:
vijay@JournalDev: ~ $ mypy vector_scale.py && python vector_scale.py Success: no issues found in 1 source file [2.0, 4.0, 6.0]
В фрагменте выше Vector — это псевдоним, обозначающий список значений с плавающей запятой. Приведенная выше программа может вывести подсказку для псевдонима.
Полный список допустимых псевдонимов приведен здесь.
Теперь давайте рассмотрим еще один пример, который проверяет каждую пару “ключ:значение” на соответствие формату name:email.
from typing import Dict import re # Create an alias called 'ContactDict' ContactDict = Dict[str, str] def check_if_valid(contacts: ContactDict) -> bool: for name, email in contacts.items(): # Check if name and email are strings if (not isinstance(name, str)) or (not isinstance(email, str)): return False # Check for email xxx@yyy.zzz if not re.match(r"[a-zA-Z0-9\._\+-]+@[a-zA-Z0-9\._-]+\.[a-zA-Z]+$", email): return False return True print(check_if_valid({'vijay': 'vijay@sample.com'})) print(check_if_valid({'vijay': 'vijay@sample.com', 123: 'wrong@name.com'}))
Вывод mypy будет выглядеть так:
vijay@JournalDev:~ $ mypy validcontacts.py validcontacts.py:19: error: Dict entry 1 has incompatible type "int": "str"; expected "str": "str" Found 1 error in 1 file (checked 1 source file)
Поскольку параметр name в нашем втором словаре является целым числом (123), то здесь мы получаем статическую ошибку времени компиляции в mypy. Таким образом, псевдонимы — это еще один способ обеспечить точную проверку типов из mypy.
Создание пользовательских типов данных с помощью NewType()
Для создания новых пользовательских типов мы можем использовать функцию NewType().
from typing import NewType # Create a new user type called 'StudentID' that consists of # an integer StudentID = NewType('StudentID', int) sample_id = StudentID(100)
Средство проверки типов будет рассматривать новый тип, как если бы он был подклассом исходного. Это помогает находить логические ошибки.
from typing import NewType # Create a new user type called 'StudentID' StudentID = NewType('StudentID', int) def get_student_name(stud_id: StudentID) -> str: return str(input(f'Enter username for ID #{stud_id}:\n')) stud_a = get_student_name(StudentID(100)) print(stud_a) # This is incorrect!! stud_b = get_student_name(-1) print(stud_b)
Вывод mypy будет иметь следующий вид:
vijay@JournalDev:~ $ mypy studentnames.py studentnames.py:13: error: Argument 1 to "get_student_name" has incompatible type "int"; expected "StudentID" Found 1 error in 1 file (checked 1 source file)
Тип Any
Any – это особый тип, который сообщает другим инструментам проверки типов (в нашем случае mypy), что каждый тип совместим с этим ключевым словом.
Рассмотрим нашу старую функцию print_list(), которая теперь принимает аргументы любого типа.
from typing import Any def print_list(a: Any) -> None: print(a) print_list([1, 2, 3]) print_list(1)
Теперь при запуске mypy ошибок не будет.
vijay@JournalDev:~ $ mypy printlist.py && python printlist.py Success: no issues found in 1 source file [1, 2, 3] 1
Все функции без возвращаемого типа значения или параметров будут по умолчанию использовать Any.
def foo(bar): return bar # A static type checker will treat the above # as having the same signature as: def foo(bar: Any) -> Any: return bar
То есть, тип Any можно использовать, чтобы смешивать статически и динамически типизированный код.
Заключение
В этой статье мы разобрались с модулем typing в Python, который очень полезен в контексте проверки типов. Он позволяет внешним средствам проверки типов, таким как mypy, точно сообщать обо всех ошибках.
Это дает нам возможность писать статически типизированный код на Python, который является языком с динамической типизацией.
Читайте также: Основы Python: полный список типов данных
Tags: Python