Введение в аннотации типов 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
.
Вы можете использовать, к примеру:
int
float
bool
bytes
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)
🤓 Other versions and variants
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)
🤓 Other versions and variants
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
🤓 Other versions and variants
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)
🤓 Other versions and variants
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-типы¶
Эти типы принимают параметры в квадратных скобках:
List
Tuple
Set
Dict
Optional
- ...и др.
называются 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
🤓 Other versions and variants
from datetime import datetime
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: 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
from datetime import datetime
from typing import 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
.