Введение в аннотации типов Python¶
Python имеет поддержку необязательных аннотаций типов.
Аннотации типов являются специальным синтаксисом, который позволяет определять тип переменной.
Объявление типов для переменных позволяет улучшить поддержку вашего кода редакторами и различными инструментами.
Это просто краткое руководство / напоминание об аннотациях типов в Python. Оно охватывает только минимум, необходимый для их использования с FastAPI... что на самом деле очень мало.
FastAPI целиком основан на аннотациях типов, у них много выгод и преимуществ.
Но даже если вы никогда не используете FastAPI, вам будет полезно немного узнать о них.
Note
Если вы являетесь экспертом в Python и уже знаете всё об аннотациях типов, переходите к следующему разделу.
Мотивация¶
Давайте начнем с простого примера:
def get_full_name(first_name, last_name):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
Вызов этой программы выводит:
John Doe
Функция делает следующее:
- Принимает
first_nameиlast_name. - Преобразует первую букву содержимого каждой переменной в верхний регистр с
title(). - Соединяет их через пробел.
def get_full_name(first_name, last_name):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
Отредактируем пример¶
Это очень простая программа.
А теперь представьте, что вы пишете её с нуля.
В какой-то момент вы бы начали определение функции, у вас были бы готовы параметры...
Но затем вы должны вызвать «тот метод, который преобразует первую букву в верхний регистр».
Было это upper? Или uppercase? first_uppercase? capitalize?
Тогда вы попробуете с давним другом программиста: автодополнением редактора.
Вы вводите первый параметр функции, first_name, затем точку (.), а затем нажимаете Ctrl+Space, чтобы запустить дополнение.
Но, к сожалению, ничего полезного не выходит:

Добавим типы¶
Давайте изменим одну строчку в предыдущей версии.
Мы изменим именно этот фрагмент, параметры функции, с:
first_name, last_name
на:
first_name: str, last_name: str
Вот и все.
Это аннотации типов:
def get_full_name(first_name: str, last_name: str):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
Это не то же самое, что объявление значений по умолчанию, например:
first_name="john", last_name="doe"
Это другая вещь.
Мы используем двоеточия (:), а не равно (=).
И добавление аннотаций типов обычно не меняет происходящего по сравнению с тем, что произошло бы без неё.
Но теперь представьте, что вы снова находитесь в процессе создания этой функции, но уже с аннотациями типов.
В тот же момент вы пытаетесь запустить автодополнение с помощью Ctrl+Space и вы видите:

При этом вы можете просматривать варианты, пока не найдёте подходящий:

Больше мотивации¶
Проверьте эту функцию, она уже имеет аннотации типов:
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + age
return name_with_age
Поскольку редактор знает типы переменных, вы получаете не только дополнение, но и проверки ошибок:

Теперь вы знаете, что вам нужно исправить, преобразовав age в строку с str(age):
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + str(age)
return name_with_age
Объявление типов¶
Вы только что видели основное место для объявления подсказок типов. В качестве параметров функции.
Это также основное место, где вы можете использовать их с FastAPI.
Простые типы¶
Вы можете объявить все стандартные типы Python, а не только str.
Вы можете использовать, к примеру:
intfloatboolbytes
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
return item_a, item_b, item_c, item_d, item_d, item_e
Generic-типы с параметрами типов¶
Существуют некоторые структуры данных, которые могут содержать другие значения, например, dict, list, set и tuple. И внутренние значения тоже могут иметь свой тип.
Чтобы объявить эти типы и внутренние типы, вы можете использовать стандартный Python-модуль typing.
Он существует специально для поддержки подсказок этих типов.
List¶
Например, давайте определим переменную как list, состоящий из str.
Импортируйте List из typing (с заглавной L):
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
Объявите переменную с тем же синтаксисом двоеточия (:).
В качестве типа укажите List.
Поскольку список является типом, содержащим некоторые внутренние типы, вы помещаете их в квадратные скобки:
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
Tip
Эти внутренние типы в квадратных скобках называются «параметрами типов».
В этом случае str является параметром типа, передаваемым в List.
Это означает: "переменная items является list, и каждый из элементов этого списка является str".
Если вы будете так поступать, редактор может оказывать поддержку даже при обработке элементов списка:

Без типов добиться этого практически невозможно.
Обратите внимание, что переменная item является одним из элементов списка items.
И все же редактор знает, что это str, и поддерживает это.
Tuple и Set¶
Вы бы сделали то же самое, чтобы объявить tuple и set:
from typing import Set, Tuple
def process_items(items_t: Tuple[int, int, str], items_s: Set[bytes]):
return items_t, items_s
Это означает:
- Переменная
items_tявляетсяtupleс 3 элементами:int, другимintиstr. - Переменная
items_sявляетсяsetи каждый элемент имеет типbytes.
Dict¶
Чтобы определить dict, вы передаёте 2 параметра типов, разделённых запятыми.
Первый параметр типа предназначен для ключей dict.
Второй параметр типа предназначен для значений dict:
from typing import Dict
def process_items(prices: Dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
Это означает:
- Переменная
pricesявляетсяdict:- Ключи этого
dictимеют типstr(скажем, название каждого элемента). - Значения этого
dictимеют типfloat(скажем, цена каждой позиции).
- Ключи этого
Optional¶
Вы также можете использовать Optional, чтобы объявить, что переменная имеет тип, например, str, но это является «необязательным», что означает, что она также может быть None:
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
Использование Optional[str] вместо просто str позволит редактору помочь вам в обнаружении ошибок, в которых вы могли бы предположить, что значение всегда является str, хотя на самом деле это может быть и None.
Generic-типы¶
Эти типы принимают параметры в квадратных скобках:
ListTupleSetDictOptional- ...и др.
называются Generic-типами или Generics.
Классы как типы¶
Вы также можете объявить класс как тип переменной.
Допустим, у вас есть класс Person с полем name:
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
Тогда вы можете объявить переменную типа Person:
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
И снова вы получаете полную поддержку редактора:

Pydantic-модели¶
Pydantic является Python-библиотекой для выполнения валидации данных.
Вы объявляете «форму» данных как классы с атрибутами.
И каждый атрибут имеет тип.
Затем вы создаете экземпляр этого класса с некоторыми значениями, и он проверяет значения, преобразует их в соответствующий тип (если все верно) и предоставляет вам объект со всеми данными.
И вы получаете полную поддержку редактора для этого итогового объекта.
Взято из официальной документации Pydantic:
from datetime import datetime
from typing import List, Union
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: Union[datetime, None] = None
friends: List[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
Info
Чтобы узнать больше о Pydantic, читайте его документацию.
FastAPI целиком основан на Pydantic.
Вы увидите намного больше всего этого на практике в Руководстве пользователя.
Аннотации типов в FastAPI¶
FastAPI получает преимущества аннотаций типов для выполнения определённых задач.
С FastAPI вы объявляете параметры с аннотациями типов и получаете:
- Поддержку редактора.
- Проверки типов.
...и FastAPI использует тот же механизм для:
- Определения требований: из параметров пути запроса, параметров запроса, заголовков, зависимостей и т.д.
- Преобразования данных: от запроса к нужному типу.
- Валидации данных: исходя из каждого запроса:
- Генерации автоматических ошибок, возвращаемых клиенту, когда данные не являются корректными.
- Документирования API с использованием OpenAPI:
- который затем используется пользовательскими интерфейсами автоматической интерактивной документации.
Всё это может показаться абстрактным. Не волнуйтесь. Вы увидите всё это в действии в Руководстве пользователя.
Важно то, что при использовании стандартных типов Python в одном месте (вместо добавления дополнительных классов, декораторов и т.д.) FastAPI сделает за вас большую часть работы.
Info
Если вы уже прошли всё руководство и вернулись, чтобы узнать больше о типах, хорошим ресурсом является «шпаргалка» от mypy.