Динамически типизированные языки отлично подходят для быстрого создания прототипов, но по мере роста кодовой базы возрастает риск ошибок типов. Чтобы уменьшить количество таких ошибок, в Python 3.5 появились подсказки типов, которые можно добавлять в код с помощью аннотаций типов, введенных в Python 3.0.
С помощью подсказок типов можно аннотировать переменные и функции типами. Python не проверяет типы во время выполнения; вместо этого инструменты статической проверки типов, такие как mypy
, pyright
или IDE, проверяют на соответствие типы и выдают предупреждения, когда типы используются несогласованно.
Использование статических средств проверки типов имеет множество преимуществ:
Статическая типизация в Python необязательна и может вводиться постепенно (это известно как постепенная типизация). При постепенной типизации статические средства проверки типов не выдают предупреждений на код без подсказок типов, также не предотвращают компиляцию несогласованных типов во время выполнения.
В этом уроке вы узнаете, как добавить подсказки типов в Python и как использовать mypy
для статической проверки типов. Вы будете аннотировать переменные, функции, списки, словари и кортежи. Вы также узнаете, как использовать протоколы, перегрузку функций и аннотирование констант.
Чтобы получить максимальную пользу от этого руководства, у вас должны быть:
Мы рекомендуем Python ≥3.10, так как в этой версии есть новые и лучшие возможности подсказки типов. Если вы используете Python ≤3.9, Python предоставляет альтернативный синтаксис подсказки типов; мы упомянем о нем в учебнике.
mypy
— это необязательная статическая проверка типов, созданная Юккой Лехтосало. Он проверяет аннотированный код в Python и выдает предупреждения, если аннотированные типы используются непоследовательно. mypy
также проверяет синтаксис кода и выдает синтаксические ошибки, если встречает неправильный синтаксис.
mypy поддерживает постепенную типизацию, позволяя вам добавлять подсказки типов в код медленно, в своем собственном темпе.
Python — это язык с сильной динамической типизацией. Сильная типизация означает, что она имеет строгие правила типизации и не приводит к неожиданным результатам.
Например, в Python вы не можете добавить целое число к строке, иначе возникнет ошибка типа:
>>> 3 + "hello" TypeError: unsupported operand type(s) for +: 'int' and 'str'
Для сравнения, язык со слабой типизацией, такой как JavaScript, выполнит неявное преобразование типов, и выполнение операции 3 + “hello” приведет к корректному результату:
> 3 + "hello"; "3hello"; // вывод
Динамическая типизация в Python означает, что интерпретатор проверяет типы только во время выполнения программы, и вы можете в любой момент изменить тип переменной:
>>> x = "hello" >>> type(x) <class 'str'> # тип переменной — строка >>> x = 3 >>> type(x) <class 'int'> # тип переменной изменен на целое число
В статически типизированных языках, таких как C и Java, когда вы присваиваете переменной тип, вы не можете изменить тип во время выполнения. Например, если вы объявите переменную как целое число, вы не сможете изменить ее на строку.
int x = 4; x = "hello"; // это вызовет ошибку типа.
В статически типизированных языках компилятор проверяет типы после написания кода. Если ошибок не обнаружено, программу можно запускать.
В Python все является объектами. Определяете ли вы строку, целое число, float
, список или словарь — все они являются объектами.
Мы можем убедиться в этом, проверив метод isinstance
:
>>> isinstance(str, object) True >>> isinstance(int, object) True
Как видите, и строка (str
), и целое число (int
) подтвердили, что являются экземплярами объекта.
Поскольку все является объектом, все также имеет атрибуты и методы. Например, если вы определите целое число, вы можете увидеть все его атрибуты, используя dir
:
>>> x = 8 >>> dir(x) [...'__add__', '__mul__', '__str__', '__divmod_', ''real', ...] # отредактировано для краткости
Методы с двойным ведущим и последующим подчеркиванием показывают операции, которые поддерживает тип int
. Например, метод __mul__
выполняет операцию умножения.
>>> x = 8 >>> x.__mul__(2) 16
По ходу статьи вы узнаете, что объекты могут быть сгруппированы в определенные подсказки типов из-за методов, которыми они обладают.
Например, объекты относятся к типу Sequence
, если у них есть методы __getitem__
и __len__
. Примерами таких объектов являются список, строка, кортеж (вывод отредактирован для краткости):
>>> dir([2,3]) # list [...'__getitem__', '__len__'...] >>> dir((3,2)) # кортеж [...'__getitem__', '__len__'...] >>> dir('hello') # строка [...'__get_item__', '__len__'...]
Можете не беспокоиться об этом, если вы не понимаете прямо сейчас — мы рассмотрим тип Sequence
в последующих разделах более подробно. Но вы должны понять, что все является объектом и имеет методы. Эти методы используются для группировки определенных объектов в определенные типы.
Исходя из этого, мы начнем аннотировать переменные в следующем разделе.
В этом разделе вы узнаете, как добавлять подсказки типов к переменным.
В Python вы можете определить переменную с подсказкой типа, используя следующий синтаксис:
имя_переменной: тип = значение
Рассмотрим следующую переменную:
name = "rocket"
Мы присваиваем переменной name строковое значение "rocket"
.
Чтобы аннотировать переменную, нам нужно добавить двоеточие (:
) после имени переменной и объявить тип str
:
name: str = "rocket"
В Python подсказки типов, определенных для переменных, можно прочитать с помощью словаря __annotations__
:
>>> name: str = "rocket" >>> __annotations__ {'name': <class 'str'>}
Словарь __annotations__
покажет вам подсказки типов для всех глобальных переменных.
Теперь вы добавили подсказку типа str
к переменной. Как уже упоминалось ранее, интерпретатор Python не принудительно определяет типы, поэтому определение переменной с неправильным типом не вызовет ошибки:
>>> name: int = "rocket" >>>
С другой стороны, статическая проверка типов, например mypy
, отметит это как ошибку:
error: Incompatible types in assignment (expression has type "str", variable has type "int")
Объявление подсказок типов для других типов данных соответствует тому же синтаксису. Ниже перечислены некоторые простые типы, которые можно использовать для аннотации переменных:
Когда вы объявляете переменную с простым типом, таким как int
или str, mypy
может определить тип. Поэтому в большинстве случаев аннотирование переменных этими простыми типами не требуется.
Однако mypy
с трудом определяет типы переменных, хранящих сложные структуры, такие как списки, словари или кортежи. И именно здесь подсказки типов для переменных становятся более важными.
Далее вы узнаете, как добавить подсказки типов к этим структурам, а пока мы добавим подсказки типов к функциям.
Теперь мы добавим подсказки типов в функции. Чтобы аннотировать функцию, мы аннотируем каждый параметр и возвращаемое значение:
def function_name(param1: param1_type, param2: param2_type) -> return_type:
Давайте аннотируем следующую функцию, которая возвращает сообщение:
def announcement(language, version): return f"{language} {version} has been released" announcement("Python", 3.10)
Функция принимает строку в качестве первого параметра и float
в качестве второго параметра. Затем она возвращает строку как результат.
Чтобы аннотировать параметры функции, мы добавим двоеточие(:
) после каждого параметра, а затем укажем тип параметра:
Чтобы указать тип возвращаемого значения, добавим ->
перед двоеточием(:
) в определении функции. Это будет выглядеть следующим образом:
def announcement(language: str, version: float) -> str: ...
Теперь функция имеет подсказки типа, показывающие, что она принимает аргументы str
и float
, а возвращает str
.
Когда вы вызовете функцию, вы получите:
result = announcement("Python", 4.11) print(result) # Python 4.11 был выпущен
Хотя в нашем коде есть подсказки типов, интерпретатор Python не выдаст предупреждения, если мы вызовем функцию с неправильными аргументами:
result = announcement(True, "Python") print(result) # True Python был выпущен
Функция выполняется успешно, несмотря на то, что в качестве первого аргумента мы передали булево True
, а в качестве второго — строку “Python”. Чтобы получать предупреждения об этих ошибках, нам нужно использовать статическую проверку типов, например mypy
.
Сейчас мы покажем, как проводить статическую проверку типов с помощью mypy
, чтобы получать предупреждения об ошибках типа в нашем коде.
Создайте каталог type_hints
и переместите его в каталог:
mkdir type_hints && cd type_hints
Создайте и активируйте виртуальную среду:
python3.10 -m venv venv source venv/bin/activate
Установите последнюю версию mypy
с помощью pip
:
pip install mypy
После установки mypy
создайте файл announcement.py
и введите в него следующий код:
def announcement(language, version): return f"{language} {version} has been released" announcement("Python", 3.10)
Сохраните файл и выйдите. Мы собираемся повторно использовать ту же функцию из предыдущего раздела.
Далее запустите файл с помощью mypy
:
mypy announcement.py Success: no issues found in 1 source file
Как вы можете видеть, mypy
не выдает никаких предупреждений. Статическая типизация в Python необязательна, и при постепенной типизации вы не должны получать никаких предупреждений, если только вы не сделаете выбор, добавив подсказки типов в функции. Это позволяет вам медленно аннотировать ваш код.
Давайте теперь разберемся, почему mypy
не показывает нам никаких предупреждений.
Как мы уже отмечали, mypy
игнорирует код без подсказок типов. Это происходит потому, что он предполагает тип Any
в коде без подсказок.
Ниже показано, как mypy
видит функцию:
def announcement(language: Any, version: Any) -> Any: return f"{language} {version} has been released" announcement("Python", 3.10)
Тип Any
— это динамический тип, который совместим с любым типом. Поэтому mypy
не будет жаловаться на то, являются ли типы аргументов функции bool, int, bytes
и т.д.
Теперь, когда мы знаем, почему mypy
не всегда выдает предупреждения, давайте настроим его для этого.
mypy
может быть настроен в соответствии с вашим рабочим процессом и практикой работы с кодом. Вы можете запустить mypy
в строгом режиме, используя опцию --strict
, чтобы отметить любой код без подсказок типов:
mypy --strict announcement.py announcement.py:1: error: Function is missing a type annotation announcement.py:4: error: Call to untyped function "print_release" in typed context Found 2 errors in 1 file (checked 1 source file)
Опция --strict
является самой ограничительной и не поддерживает постепенную типизацию. В большинстве случаев нам не нужно быть настолько строгими. Вместо этого мы хотим использовать постепенную типизацию, чтобы добавлять подсказки типов поэтапно.
mypy
также предоставляет опцию --disallow-incomplete-defs
. Эта опция помечает функции, у которых не аннотированы все параметры и возвращаемые значения. Эта опция очень удобна, потому что если вы забыли аннотировать возвращаемое значение или вновь добавленный параметр, mypy
предупредит вас об этом.
Чтобы понять это, давайте добавим подсказки типов только к параметрам и опустим типы возвращаемых значений (представим, что мы забыли):
def announcement(language: str, version: float): return f"{language} {version} has been released" announcement("Python", 3.10)
Запустите файл с помощью mypy
без каких-либо опций командной строки:
mypy announcement.py Success: no issues found in 1 source file
Как видите, mypy
не предупреждает нас о том, что мы забыли указать тип возврата. Он предполагает тип Any
в возвращаемом значении. Если бы функция была большой, было бы трудно определить тип возвращаемого значения. Чтобы узнать тип, нам пришлось бы исследовать возвращаемое значение, что отнимает много времени.
Чтобы защитить себя от этих проблем, мы передадим опцию --disallow-incomplete-defs в mypy
:
mypy --disallow-incomplete-defs announcement.py announcement.py:1: error: Function is missing a return type annotation Found 1 error in 1 file (checked 1 source file
Теперь mypy
предупреждает нас, что мы пропустили аннотацию возвращаемого типа, поэтому давайте добавим ее:
def announcement(language: str, version: float) -> str: ...
Запустите файл снова с включенной опцией --disallow-incomplete-defs
:
mypy --disallow-incomplete-defs announcement.py Success: no issues found in 1 source file
На этот раз mypy
работает без проблем, потому что мы закончили аннотирование определений функций.
Давайте теперь вернемся к тому, что произошло в предыдущем разделе, когда мы только учились добавлять подсказки типов к функциям. Мы отметили, что в Python нет принудительного указания типов, и что следующий код будет выполняться без проблем:
ef announcement(language: str, version: float) -> str: return f"{language} {version} has been released" announcement(True, "Python") # bad arguments
Давайте посмотрим, будет ли mypy
теперь предупреждать нас об этом:
mypy --disallow-incomplete-defs announcement.py announcement.py:4: error: Argument 1 to "print_release" has incompatible type "bool"; expected "str" announcement.py:4: error: Argument 2 to "print_release" has incompatible type "str"; expected "float" Found 2 errors in 1 file (checked 1 source file)
Отлично! mypy
предупреждает нас, что мы передали в функцию неправильные аргументы.
Теперь давайте избавимся от необходимости набирать mypy
с опцией —disallow-incomplete-defs
.
mypy позволяет сохранять опции в файле mypy.ini
. При запуске mypy
будет проверять этот файл и запускаться с опциями, сохраненными в файле.
Создайте файл mypy.ini
в корневом каталоге проекта и введите следующий код:
[mypy] python_version = 3.10 disallow_incomplete_defs = True
В файле mypy.ini
мы сообщаем mypy
, что мы используем Python 3.10 и что мы хотим запретить неполные определения функций.
Сохраните файл в своем проекте, и в следующий раз вы сможете запустить mypy
без каких-либо опций командной строки:
mypy announcement.py Success: no issues found in 1 source file
У mypy
есть множество опций, которые вы можете добавить в файл mypy
. Я рекомендую обратиться к документации по командной строке mypy
, чтобы узнать больше.
Теперь вы узнали, как выполнять статическую проверку типов с помощью mypy
. Далее мы добавим подсказки типов в функцию без оператора возврата.
Не все функции имеют оператор возврата. Когда вы создаете функцию без оператора возврата, она все равно возвращает значение None
:
def announcement(language: str, version: float): print(f"{language} {version} has been released") result = announcement("Python", 4.11) print(result) # None
Значение None
не является полезным, поскольку оно не предназначено для использования. Оно лишь показывает, что функция выполнилась успешно.
Чтобы показать, что функция ничего не возвращает, мы можем аннотировать возвращаемое значение функции значением None
:
def announcement(language: str, version: float) -> None: ...
Добавление подсказок о типе объединения в параметрах функции
Когда функция принимает параметр более чем одного типа, вы можете использовать символ объединения (|
) для разделения типов.
Например, следующая функция принимает параметр, который может быть либо str
, либо int
:
def show_type(num): if(isinstance(num, str)): print("You entered a string") elif (isinstance(num, int)): print("You entered an integer") show_type('hello') # You entered a string show_type(3) # You entered an integer
Функция show_type
может быть вызвана со строкой или целым числом, и будет выводить результат в зависимости от типа значения, переданного в качестве аргумента.
Для аннотации параметра мы будем использовать символ объединения |
, который был введен в Python 3.10, для разделения типов следующим образом:
def show_type(num: str | int) -> None: ... show_type('hello') show_type(3)
Союз | теперь показывает, что параметр num
является либо str
, либо int
.
Если вы используете Python ≤3.9, вам необходимо импортировать Union
из модуля типизации. Параметр может быть аннотирован следующим образом:
from typing import Union def show_type(num: Union[str, int]) -> None: ...
Не все параметры в функции обязательны; некоторые из них необязательны. Пример функции, принимающей необязательный параметр, приведен ниже:
def format_name(name: str, title = None) -> str: if title: return f"Name: {title}. {name.title()}" else: return f"Name: {name.title()}" format_name("john doe", "Mr")
Второй аргумент title
является необязательным параметром. Необязательным его делает установка параметра title
в значение по умолчанию — None
, в данном случае. Параметр может принимать строку, а может принимать значение по умолчанию None
, если аргумент не указан.
Чтобы указать, что title
является необязательным, мы воспользуемся типом Optional[str]
из модуля typing
:
from typing import Optional def format_name(name: str, title: Optional[str] = None) -> str: ... format_name("john doe", "Mr")
Теперь мы аннотировали необязательный параметр title
типом Optional
. В следующем разделе мы добавим подсказки типов к спискам.
В этой вводной части мы рассмотрели основы аннотирования типов и связанной с ним теории и инструментов. Приглашаем прочитать эту вторую часть, где мы рассмотрим более продвинутые примеры работы с аннотациями в режиме объектно-ориентированного программирования.
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…