Модуль typing в Python
Модуль typing в Python, представленный в Python 3.5, пытается предоставить способ подсказки типов, чтобы помочь средствам проверки статических типов и линтерам точно предсказывать ошибки.
Из-за того, что Python должен определять тип объектов во время выполнения, разработчикам иногда очень сложно узнать, что именно происходит в коде.
Даже внешние средства проверки типов, такие как PyCharm IDE, не дают лучших результатов. В среднем только правильно прогнозирует ошибки примерно в 50% случаев, согласно этому ответу на StackOverflow.
Python пытается смягчить эту проблему, вводя так называемое указание типа (аннотация типа), чтобы помочь внешним средствам проверки типов идентифицировать любые ошибки. Это хороший способ для программиста намекнуть на тип используемого объекта (ов) во время самой компиляции и убедиться, что средства проверки типов работают правильно.
Это также делает код Python более читабельным и надежным для других читателей!
ПРИМЕЧАНИЕ. Фактическая проверка типов во время компиляции не выполняется. Если фактический возвращенный объект не был того же типа, что и намекаемый, ошибки компиляции не будет. Вот почему мы используем внешние средства проверки типов, такие как mypy, для выявления любых ошибок типов.
Рекомендуемые предпосылки
Для эффективного использования модуля набора текста рекомендуется использовать внешнюю программу проверки типов или линтер для проверки соответствия статического типа. Mypy – одна из наиболее широко используемых в Python проверок типов, поэтому я рекомендую вам установить ее перед чтением остальной части статьи.
В этой статье мы будем использовать mypy в качестве средства проверки статического типа, которое можно установить с помощью:
Вы можете запустить mypy для любого файла в Python, чтобы проверить, совпадают ли типы.
После отладки ошибок вы можете запустить программу в обычном режиме, используя:
Теперь, когда у нас есть все необходимое, давайте попробуем использовать некоторые функции модуля.
Типовые подсказки и аннотации
О функциях
Мы можем аннотировать функцию, чтобы указать ее возвращаемый тип и типы ее параметров.
def print_list(a: list) -> None: print(a)
Это информирует средство проверки типов (в моем случае mypy), что у нас есть функция print_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__)
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(): , 'return': >
Типовые псевдонимы
Модуль набора текста предоставляет нам псевдонимы типов, которые определяются путем присвоения типа псевдониму.
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] a = scale(scalar=2.0, vector=[1.0, 2.0, 3.0]) print(a)
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 – это псевдоним, обозначающий список значений с плавающей запятой. Мы можем ввести подсказку для псевдонима, что и делает вышеуказанная программа.
Давайте рассмотрим еще один пример, который проверяет каждую пару key:value в словаре и проверяет, соответствуют ли они формату 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()) print(check_if_valid())
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)
Здесь мы получаем статическую ошибку времени компиляции в mypy, поскольку параметр name во втором словаре является целым числом (123). Таким образом, псевдонимы – это еще один способ обеспечить точную проверку типов в 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 #:\n')) stud_a = get_student_name(StudentID(100)) print(stud_a) # This is incorrect!! stud_b = get_student_name(-1) print(stud_b)
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)
Любой тип
Это особый тип, информирующий средство проверки статического типа (в моем случае 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 для смешивания статически и динамически типизированного кода.
Заключение
В этой статье мы узнали о модуле ввода текста Python, который очень полезен в контексте проверки типов, позволяя внешним средствам проверки типов, таким как mypy, точно сообщать о любых ошибках.
Это дает нам возможность писать статически типизированный код на Python, который по дизайну является языком с динамической типизацией.