Поиск по сайту:

Модуль ввода Python — эффективное использование средств проверки типов


Появившийся начиная с Python 3.5, модуль Python typing пытается предоставить способ указания типов, чтобы помочь программам проверки статических типов и линтерам точно предсказывать ошибки.

Из-за того, что Python должен определять тип объектов во время выполнения, разработчикам иногда очень сложно выяснить, что именно происходит в коде.

Даже внешние средства проверки типов, такие как PyCharm IDE, не дают наилучших результатов; согласно этому ответу на StackOverflow, в среднем только правильно прогнозирует ошибки примерно в 50% случаев.

Python пытается смягчить эту проблему, вводя так называемую подсказку типа (аннотацию типа), чтобы помочь внешним средствам проверки типов выявлять любые ошибки. Это хороший способ для программиста указать тип используемого объекта (объектов) во время самой компиляции и убедиться, что средства проверки типов работают правильно.

Это делает код Python намного более читабельным и надежным для других читателей!

ПРИМЕЧАНИЕ. Это не выполняет фактическую проверку типов во время компиляции. Если реальный возвращенный объект не был того же типа, что и подсказка, ошибки компиляции не будет. Вот почему мы используем внешние средства проверки типов, такие как mypy, для выявления любых ошибок типов.

Рекомендуемые предпосылки

Для эффективного использования модуля typing рекомендуется использовать внешнюю проверку типов/линтер для проверки статического соответствия типов. Одной из наиболее широко используемых программ проверки типов для Python является mypy, поэтому я рекомендую вам установить ее, прежде чем читать оставшуюся часть статьи.

Мы уже рассмотрели основы проверки типов в Python. Вы можете сначала пройтись по этой статье.

В этой статье мы будем использовать mypy в качестве средства проверки статического типа, которое можно установить:

pip3 install mypy

Вы можете запустить mypy для любого файла Python, чтобы проверить соответствие типов. Это как если бы вы «компилировали» код Python.

mypy program.py

После отладки ошибок вы можете запустить программу в обычном режиме, используя:

python program.py

Теперь, когда у нас есть необходимые условия, давайте попробуем использовать некоторые функции модуля.

Типовые подсказки/Типовые аннотации

О функциях

Мы можем аннотировать функцию, чтобы указать ее возвращаемый тип и типы ее параметров.

def print_list(a: list) -> None:
    print(a)

Это информирует средство проверки типов (в моем случае mypy), что у нас есть функция print_list(), которая принимает список в качестве аргумента и вернуть Нет.

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]

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 — это псевдоним, обозначающий список значений с плавающей запятой. Мы можем ввести намек на псевдоним, что и делает приведенная выше программа.

Полный список допустимых псевдонимов приведен здесь.

Давайте рассмотрим еще один пример, который проверяет каждую пару ключ:значение в словаре и проверяет, соответствуют ли они формату имя:электронная почта.

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)

Здесь мы получаем статическую ошибку времени компиляции в 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 #{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)

Любой тип

Это особый тип, информирующий средство проверки статических типов (в моем случае 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, который по своей природе является языком с динамической типизацией!

Рекомендации

  • Документация по Python для модуля ввода (содержит подробную информацию о других методах этого модуля, и я рекомендую ее в качестве дополнительного справочника)
  • Вопрос StackOverflow о подсказках типов (это дает очень хорошее обсуждение темы. Я настоятельно рекомендую вам также прочитать эту тему!)