Типы в питоне (Python) и их проверка
Проверка типов — это новая функция Python, которая была добавлена в Python 3.5. Она также известна как аннотация типа. Она позволяет добавлять к функциям, объявлениям переменных специальный синтаксис, который информирует разработчика, какой тип имеет аргумент или переменная.
Python не применяет подсказки типов. Но некоторые среды разработки, такие как PyCharm, поддерживают подсказки типов и выделяют ошибки ввода.
Плюсы и минусы подсказок типов
Преимущества использования подсказок типов:
- Это отличный способ документировать код.
- Подсказки типов могут сделать IDE и линтеры более отзывчивыми.
- Повышают качество разрабатываемого кода.
В то же время подсказки типов имеют и недостатки:
- Увеличивается объем кода.
- Подсказки типов увеличивают время, затрачиваемое на разработку.
- Они работают только в Python 3.5+.
- Могут привести к потерям времени при запуске кода. Особенно если вы импортируете модульtyping.
Когда необходимо использовать подсказки типов:
- При написании объемного кода.
- При создании библиотек, предназначенных для использования другими разработчиками.
- При написании модульных тестов.
Встроенные подсказки типов
В Python можно добавить подсказку для следующих типов данных:
- Int.
- Float.
- Bool.
- Str.
- Bytes.
Они могут использоваться как в функциях, так и в аннотациях переменных. Концепция аннотации переменной была добавлена в Python 3.6.
Несколько примеров:
x: int # переменная с именем x без инициализации
y: float = 1.0 # переменная типа float, инициализированная значением 1,0
z: bool = False
a: str = 'Подсказка типа Hello'
Можно добавить подсказку типа в переменную, не инициализируя ее, как это было сделано в первой строке кода. Другие три строки кода демонстрируют, как аннотировать каждую переменную и правильно их инициализировать.
Подсказки типов для наборов
Коллекция в Python представляет собой группу элементов. Поддерживаемые типы коллекций и последовательностей: list, dict, tuple и set. Но нельзя комментировать переменные, используя эти встроенные типы. Вместо этого вы должны использовать модуль typing.
Рассмотрим несколько примеров:
>>> from typing import List
>>> names: List[str] = ['Mike']
>>> names
['Mike']
Мы создали list с одной str в нем. Это указывает, что вы создаете list, состоящий из строк. Также можно указать тип каждого элемента в списке:
>>> from typing import List
>>> names: List[str, str] = ['Mike', 'James']
Подсказки для кортежей похожи на подсказки списков:
>>> from typing import Tuple
>>> s: Tuple[int, float, str] = (5, 3.14, 'hello')
Подсказки типов для словарей отличаются тем, что в них не нужно указывать тип ключа и значения:
>>> from typing import Dict
>>> d: Dict[str, int] = {'one': 1}
Если коллекция имеет непостоянный размер, можно использовать кортежи:
>>> from typing import Tuple
>>> t: Tuple[int, ...] = (4, 5, 6)
Значение подсказки, которое является None
Иногда значение переменной нужно инициализировать как None. Для этого можно использовать Optional:
>>> from typing import Optional
>>> result: Optional[str] = my_function()
Если значение переменной не может быть None, нужно добавить в код assert:
>>> assert result is not None
Подсказки типов для функций
Использование подсказок в функциях аналогично их применению для переменных. Но при этом вы также можете добавить в функцию тип возвращаемого значения:
def adder(x: int, y: int) -> None:
print(f'The total of {x} + {y} = {x+y}')
В этом примере adder() принимает два аргумента: x и y. Они оба должны быть целыми числами. Тип возвращаемого значения None указывается с помощью -> после заключительных скобок, но перед двоеточием.
Допустим, что нужно присвоить переменной функцию adder(). Вы можете аннотировать переменную Callable следующим образом:
from typing import Callable
def adder(x: int, y: int) -> None:
print(f'The total of {x} + {y} = {x+y}')
a: Callable[[int, int], None] = adder
Переменная Callable принимает список аргументов для функции. Это также позволяет указать тип возвращаемого значения.
Рассмотрим еще один пример, в котором мы передаем более сложные аргументы:
from typing import Tuple, Optional
def some_func(x: int, y: Tuple[str, str],
z: Optional[float]: = None): -> Optional[str]:
if x > 10:
return None
return 'You called some_func'
Мы создали функцию some_func(), которая принимает три аргумента:
- int;
- tuple строк из двух элементов;
- необязательный аргумент float, который по умолчанию является
При использовании подсказок типа в функции со значением по умолчанию нужно добавить пробел до и после знака равенства.
Это также возвращает либо None, либо строку.
Что делать, когда все усложняется
Но что делать, если передаваемый аргумент может быть нескольких типов? В этом случае можно использовать Union:
>>> from typing import Union
>>> z: Union[str, int]
Эта подсказка типа означает, что переменная z может быть строкой или целым числом.
Также есть случаи, когда функция может принимать объект. Если этот объект может быть одним из нескольких объектов, то можно использовать Any.
x: Any = some_function()
Классы
Пример использования подсказки типа для класса:
>>> class Test:
... pass
...
>>> t: Test = Test()
Это полезно при передаче экземпляров класса между функциями или методами.
Декораторы
Декораторы – это функции, которые принимают другие функции и модифицируют их. Добавление подсказок к декораторам выглядит нестандартно. Например:
>>> from typing import Any, Callable, TypeVar, cast
>>> F = TypeVar('F', bound=Callable[..., Any])
>>> def my_decorator(func: F) -> F:
def wrapper(*args, **kwds):
print("Calling", func)
return func(*args, **kwds)
return cast(F, wrapper)
TypeVar — это способ указать пользовательский тип. Для этого создается пользовательский тип Callable, который может принимать любое количество аргументов и возвращает Any. Затем создается декоратор и добавляется новый тип в качестве подсказки типа для первого аргумента и возвращаемого типа.
Функция cast используется только Mypy – утилитой проверки статического кода. Она предназначена для приведения значения к указанному типу. В данном случае вы приводите функцию wrapper к типу F.
Псевдонимы
В Python можно создать новое имя для типа. Например, переименовать тип List в Vector:
>>> from typing import List
>>> Vector = List[int]
>>> def some_function(a: Vector) -> None:
... print(a)
Теперь Vector и List ссылаются на однотипные подсказки. Псевдонимы подсказок типов полезны для сложных типов.
В документации по типам есть хороший пример, который приведен ниже:
from typing import Dict, Tuple
ConnectionOptions = Dict[str, str]
Address = Tuple[str, int]
Server = Tuple[Address, ConnectionOptions]
Этот код позволяет вкладывать типы в другие типы, при этом сохраняя возможность добавлять соответствующие подсказки типов.
Другие подсказки типов
Есть несколько других подсказок типов. Например, универсальные изменяемые типыMutableMapping, которые можно использовать для настраиваемого изменяемого словаря.
Полная документация по различным типам подсказок доступна здесь.
Комментарии типов
Подсказки типов не поддерживаются в Python 2. Но можно использовать синтаксис, аналогичный комментариям:
def some_function(a):
# type: str -> None
print(a)
При этом нужно, чтобы комментарий начинался с type:. Эта строка должна быть в той же или следующей строке кода, на который она указывает. Если функция принимает несколько аргументов, подсказки разделяются запятыми:
def some_function(a, b, c):
# type: (str, int, int) -> None
print(a)
Некоторые IDE Python вместо этого поддерживают подсказки типов в строке документации. Например, PyCharm позволяет сделать следующее:
def some_function(a, b):
"""
@type a: int
@type b: float
"
Mypy будет работать с другими комментариями, но не с этими. Если вы работаете в PyCharm, то можете использовать любую форму подсказок типов.
Статическая проверка типа
Чтобы запустить Mypy в своем коде, необходимо установить его, используя pip:
$ pip install mypy
Затем запустить его следующей командой:
$ mypy my_program.py
После этого Mypy будет работать с вашим кодом, и выводить любые ошибки типа. Он делает это без запуска кода.
Если в программе нет подсказок типа, то Mypy не будет выводить отчет об ошибках.
Напишем функцию подсказок неверного типа и сохраним ее в файл bad_type_hinting.py:
# bad_type_hinting.py
def my_function(a: str, b: str) -> None:
return a.keys() + b.keys()
Теперь можно запустить Mypy:
$ mypy bad_type_hinting.py
bad_type_hinting.py:4: error: "str" has no attribute "keys"
Found 1 error in 1 file (checked 1 source file)
Мы получим сообщение о проблеме в строке 4. Строка не содержит атрибут keys(). Обновим код, чтобы удалить вызовы несуществующего метода keys(). Сохраните эти изменения в новом файле с именем bad_type_hinting2.py:
# bad_type_hinting2.py
def my_function(a: str, b: str) -> None:
return a + b
Снова запустим Mypy:
$ mypy bad_type_hinting2.py
bad_type_hinting2.py:4: error: No return value expected
Found 1 error in 1 file (checked 1 source file)
Ошибка все еще не устранена. На этот раз можно исправить код так, чтобы он ничего не возвращал. А также исправить подсказку, чтобы она возвращала str.
Сохраним следующий код в файле с именем good_type_hinting.py:
# good_type_hinting.py
def my_function(a: str, b: str) -> str:
return a + b
Снова запустим Mypy:
$ mypy good_type_hinting.py
Success: no issues found in 1 source file
На этот раз в коде нет проблем.
Заключение
Вот несколько источников для эффективного использования подсказок типов.
- Справочник Mypyпо подсказкам типов.
- Typeshed
- Документация по модулю типов
Подсказки типов не обязательны в Python. Но они добавляют ясность и могут оказаться действительно полезными.