# Введение в типы Python { #python-types-intro }

Python поддерживает необязательные «подсказки типов» (их также называют «аннотациями типов»).

Эти **«подсказки типов»** или аннотации — это специальный синтаксис, позволяющий объявлять <abbr title="например: str, int, float, bool">тип</abbr> переменной.

Объявляя типы для ваших переменных, редакторы кода и инструменты смогут лучше вас поддерживать.

Это всего лишь **краткое руководство / напоминание** о подсказках типов в Python. Оно охватывает только минимум, необходимый для их использования с **FastAPI**... что на самом деле очень мало.

**FastAPI** целиком основан на этих подсказках типов — они дают ему множество преимуществ и выгод.

Но даже если вы никогда не используете **FastAPI**, вам будет полезно немного узнать о них.

/// note | Примечание

Если вы являетесь экспертом в Python и уже знаете всё о подсказках типов, переходите к следующей главе.

///

## Мотивация { #motivation }

Давайте начнем с простого примера:

{* ../../docs_src/python_types/tutorial001_py39.py *}

Вызов этой программы выводит:

```
John Doe
```

Функция делает следующее:

* Принимает `first_name` и `last_name`.
* Преобразует первую букву каждого значения в верхний регистр с помощью `title()`.
* <abbr title="Объединяет в одно целое. Содержимое одного — сразу после другого.">Соединяет</abbr> их пробелом посередине.

{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}

### Отредактируем пример { #edit-it }

Это очень простая программа.

А теперь представьте, что вы пишете её с нуля.

В какой-то момент вы бы начали определение функции, у вас были бы готовы параметры...

Но затем нужно вызвать «тот метод, который делает первую букву заглавной».

Это был `upper`? Или `uppercase`? `first_uppercase`? `capitalize`?

Тогда вы пробуете старого друга программиста — автозавершение редактора кода.

Вы вводите первый параметр функции, `first_name`, затем точку (`.`) и нажимаете `Ctrl+Space`, чтобы вызвать автозавершение.

Но, к сожалению, ничего полезного не находится:

<img src="/img/python-types/image01.png">

### Добавим типы { #add-types }

Давайте изменим одну строку из предыдущей версии.

Мы поменяем ровно этот фрагмент — параметры функции — с:

```Python
    first_name, last_name
```

на:

```Python
    first_name: str, last_name: str
```

Вот и всё.

Это и есть «подсказки типов»:

{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}

Это не то же самое, что объявление значений по умолчанию, как, например:

```Python
    first_name="john", last_name="doe"
```

Это другая вещь.

Здесь мы используем двоеточия (`:`), а не знак равенства (`=`).

И добавление подсказок типов обычно не меняет поведение программы по сравнению с вариантом без них.

Но теперь представьте, что вы снова посередине написания этой функции, только уже с подсказками типов.

В тот же момент вы пробуете вызвать автозавершение с помощью `Ctrl+Space` — и видите:

<img src="/img/python-types/image02.png">

С этим вы можете прокручивать варианты, пока не найдёте тот самый:

<img src="/img/python-types/image03.png">

## Больше мотивации { #more-motivation }

Посмотрите на эту функцию — у неё уже есть подсказки типов:

{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}

Так как редактор кода знает типы переменных, вы получаете не только автозавершение, но и проверки ошибок:

<img src="/img/python-types/image04.png">

Теперь вы знаете, что нужно исправить — преобразовать `age` в строку с помощью `str(age)`:

{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}

## Объявление типов { #declaring-types }

Вы только что увидели основное место, где объявляют подсказки типов — параметры функции.

Это также основное место, где вы будете использовать их с **FastAPI**.

### Простые типы { #simple-types }

Вы можете объявлять все стандартные типы Python, не только `str`.

Можно использовать, например:

* `int`
* `float`
* `bool`
* `bytes`

{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}

### Generic-типы с параметрами типов { #generic-types-with-type-parameters }

Есть структуры данных, которые могут содержать другие значения, например, `dict`, `list`, `set` и `tuple`. И внутренние значения тоже могут иметь свой тип.

Такие типы, которые содержат внутренние типы, называют «**generic**»-типами. И их можно объявлять, в том числе с указанием внутренних типов.

Чтобы объявлять эти типы и их внутренние типы, вы можете использовать стандартный модуль Python `typing`. Он существует специально для поддержки подсказок типов.

#### Новые версии Python { #newer-versions-of-python }

Синтаксис с использованием `typing` **совместим** со всеми версиями, от Python 3.6 до самых новых, включая Python 3.9, Python 3.10 и т.д.

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

Если вы можете выбрать более свежую версию Python для проекта, вы получите дополнительную простоту.

Во всей документации есть примеры, совместимые с каждой версией Python (когда есть различия).

Например, «**Python 3.6+**» означает совместимость с Python 3.6 и выше (включая 3.7, 3.8, 3.9, 3.10 и т.д.). А «**Python 3.9+**» — совместимость с Python 3.9 и выше (включая 3.10 и т.п.).

Если вы можете использовать **последние версии Python**, используйте примеры для самой новой версии — у них будет **самый лучший и простой синтаксис**, например, «**Python 3.10+**».

#### List { #list }

Например, давайте определим переменную как `list` из `str`.

Объявите переменную с тем же синтаксисом двоеточия (`:`).

В качестве типа укажите `list`.

Так как список — это тип, содержащий внутренние типы, укажите их в квадратных скобках:

{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}

/// info | Информация

Эти внутренние типы в квадратных скобках называются «параметрами типов».

В данном случае `str` — это параметр типа, передаваемый в `list`.

///

Это означает: «переменная `items` — это `list`, и каждый элемент этого списка — `str`».

Таким образом, ваш редактор кода сможет помогать даже при обработке элементов списка:

<img src="/img/python-types/image05.png">

Без типов добиться этого почти невозможно.

Обратите внимание, что переменная `item` — один из элементов списка `items`.

И всё же редактор кода знает, что это `str`, и даёт соответствующую поддержку.

#### Tuple и Set { #tuple-and-set }

Аналогично вы бы объявили `tuple` и `set`:

{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}

Это означает:

* Переменная `items_t` — это `tuple` из 3 элементов: `int`, ещё один `int` и `str`.
* Переменная `items_s` — это `set`, и каждый элемент имеет тип `bytes`.

#### Dict { #dict }

Чтобы определить `dict`, вы передаёте 2 параметра типов, разделённые запятой.

Первый параметр типа — для ключей `dict`.

Второй параметр типа — для значений `dict`:

{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}

Это означает:

* Переменная `prices` — это `dict`:
    * Ключи этого `dict` имеют тип `str` (скажем, название каждой позиции).
    * Значения этого `dict` имеют тип `float` (скажем, цена каждой позиции).

#### Union { #union }

Вы можете объявить, что переменная может быть **одним из нескольких типов**, например, `int` или `str`.

В Python 3.6 и выше (включая Python 3.10) вы можете использовать тип `Union` из `typing` и перечислить в квадратных скобках все допустимые типы.

В Python 3.10 также появился **новый синтаксис**, где допустимые типы можно указать через <abbr title='также называется «побитовый оператор OR», но это значение здесь нерелевантно'>вертикальную черту (`|`)</abbr>.

//// tab | Python 3.10+

```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial008b_py310.py!}
```

////

//// tab | Python 3.9+

```Python hl_lines="1  4"
{!> ../../docs_src/python_types/tutorial008b_py39.py!}
```

////

В обоих случаях это означает, что `item` может быть `int` или `str`.

#### Возможно `None` { #possibly-none }

Вы можете объявить, что значение может иметь определённый тип, например `str`, но также может быть и `None`.

В Python 3.6 и выше (включая Python 3.10) это можно объявить, импортировав и используя `Optional` из модуля `typing`.

```Python hl_lines="1  4"
{!../../docs_src/python_types/tutorial009_py39.py!}
```

Использование `Optional[str]` вместо просто `str` позволит редактору кода помочь вам обнаружить ошибки, когда вы предполагаете, что значение всегда `str`, хотя на самом деле оно может быть и `None`.

`Optional[Something]` — это на самом деле сокращение для `Union[Something, None]`, они эквивалентны.

Это также означает, что в Python 3.10 вы можете использовать `Something | None`:

//// tab | Python 3.10+

```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial009_py310.py!}
```

////

//// tab | Python 3.9+

```Python hl_lines="1  4"
{!> ../../docs_src/python_types/tutorial009_py39.py!}
```

////

//// tab | Python 3.9+ альтернативный вариант

```Python hl_lines="1  4"
{!> ../../docs_src/python_types/tutorial009b_py39.py!}
```

////

#### Использовать `Union` или `Optional` { #using-union-or-optional }

Если вы используете версию Python ниже 3.10, вот совет с моей весьма **субъективной** точки зрения:

* 🚨 Избегайте использования `Optional[SomeType]`
* Вместо этого ✨ **используйте `Union[SomeType, None]`** ✨.

Оба варианта эквивалентны и внутри одинаковы, но я бы рекомендовал `Union` вместо `Optional`, потому что слово «**optional**» («необязательный») может навести на мысль, что значение необязательное, хотя на самом деле оно означает «может быть `None`», даже если параметр не является необязательным и всё ещё обязателен.

Мне кажется, `Union[SomeType, None]` более явно выражает смысл.

Речь только о словах и названиях. Но эти слова могут влиять на то, как вы и ваши коллеги думаете о коде.

В качестве примера возьмём эту функцию:

{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}

Параметр `name` определён как `Optional[str]`, но он **не необязательный** — вы не можете вызвать функцию без этого параметра:

```Python
say_hi()  # О нет, это вызывает ошибку! 😱
```

Параметр `name` всё ещё **обязателен** (не *optional*), потому что у него нет значения по умолчанию. При этом `name` принимает `None` как значение:

```Python
say_hi(name=None)  # Это работает, None допустим 🎉
```

Хорошая новость: как только вы перейдёте на Python 3.10, об этом можно не переживать — вы сможете просто использовать `|` для объединения типов:

{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}

И тогда вам не придётся задумываться о названиях вроде `Optional` и `Union`. 😎

#### Generic-типы { #generic-types }

Типы, которые принимают параметры типов в квадратных скобках, называются **Generic-типами** или **Generics**, например:

//// tab | Python 3.10+

Вы можете использовать те же встроенные типы как generics (с квадратными скобками и типами внутри):

* `list`
* `tuple`
* `set`
* `dict`

И, как и в предыдущих версиях Python, из модуля `typing`:

* `Union`
* `Optional`
* ...и другие.

В Python 3.10, как альтернативу generics `Union` и `Optional`, можно использовать <abbr title='также называется «побитовый оператор OR», но это значение здесь нерелевантно'>вертикальную черту (`|`)</abbr> для объявления объединений типов — это гораздо лучше и проще.

////

//// tab | Python 3.9+

Вы можете использовать те же встроенные типы как generics (с квадратными скобками и типами внутри):

* `list`
* `tuple`
* `set`
* `dict`

И generics из модуля `typing`:

* `Union`
* `Optional`
* ...и другие.

////

### Классы как типы { #classes-as-types }

Вы также можете объявлять класс как тип переменной.

Допустим, у вас есть класс `Person` с именем:

{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}

Тогда вы можете объявить переменную типа `Person`:

{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}

И снова вы получите полную поддержку редактора кода:

<img src="/img/python-types/image06.png">

Обратите внимание, что это означает: «`one_person` — это **экземпляр** класса `Person`».

Это не означает: «`one_person` — это **класс** с именем `Person`».

## Pydantic-модели { #pydantic-models }

<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> — это библиотека Python для валидации данных.

Вы объявляете «форму» данных как классы с атрибутами.

И у каждого атрибута есть тип.

Затем вы создаёте экземпляр этого класса с некоторыми значениями — он провалидирует значения, преобразует их к соответствующему типу (если это применимо) и вернёт вам объект со всеми данными.

И вы получите полную поддержку редактора кода для этого результирующего объекта.

Пример из официальной документации Pydantic:

{* ../../docs_src/python_types/tutorial011_py310.py *}

/// info | Информация

Чтобы узнать больше о <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic, ознакомьтесь с его документацией</a>.

///

**FastAPI** целиком основан на Pydantic.

Вы увидите намного больше всего этого на практике в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.

/// tip | Совет

У Pydantic есть особое поведение, когда вы используете `Optional` или `Union[Something, None]` без значения по умолчанию. Подробнее читайте в документации Pydantic: <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">Required Optional fields</a>.

///

## Подсказки типов с аннотациями метаданных { #type-hints-with-metadata-annotations }

В Python также есть возможность добавлять **дополнительные <abbr title="Данные о данных, в данном случае — информация о типе, например описание.">метаданные</abbr>** к подсказкам типов с помощью `Annotated`.

Начиная с Python 3.9, `Annotated` входит в стандартную библиотеку, поэтому вы можете импортировать его из `typing`.

{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}

Сам Python ничего не делает с `Annotated`. А для редакторов кода и других инструментов тип по-прежнему `str`.

Но вы можете использовать это место в `Annotated`, чтобы передать **FastAPI** дополнительные метаданные о том, как вы хотите, чтобы ваше приложение себя вело.

Важно помнить, что **первый параметр типа**, который вы передаёте в `Annotated`, — это **фактический тип**. Всё остальное — просто метаданные для других инструментов.

Пока вам достаточно знать, что `Annotated` существует и это — стандартный Python. 😎

Позже вы увидите, насколько это **мощно**.

/// tip | Совет

Тот факт, что это **стандартный Python**, означает, что вы по-прежнему получите **лучший возможный разработческий опыт** в вашем редакторе кода, с инструментами для анализа и рефакторинга кода и т.д. ✨

А ещё ваш код будет очень совместим со множеством других инструментов и библиотек Python. 🚀

///

## Аннотации типов в **FastAPI** { #type-hints-in-fastapi }

**FastAPI** использует эти подсказки типов для выполнения нескольких задач.

С **FastAPI** вы объявляете параметры с подсказками типов и получаете:

* **Поддержку редактора кода**.
* **Проверки типов**.

...и **FastAPI** использует эти же объявления для:

* **Определения требований**: из path-параметров пути запроса, query-параметров, HTTP-заголовков, тел запросов, зависимостей и т.д.
* **Преобразования данных**: из HTTP-запроса к требуемому типу.
* **Валидации данных**: приходящих с каждого HTTP-запроса:
    * Генерации **автоматических ошибок**, возвращаемых клиенту, когда данные некорректны.
* **Документирования** API с использованием OpenAPI:
    * что затем используется пользовательскими интерфейсами автоматической интерактивной документации.

Всё это может звучать абстрактно. Не волнуйтесь. Вы увидите всё это в действии в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}.

Важно то, что, используя стандартные типы Python в одном месте (вместо добавления дополнительных классов, декораторов и т.д.), **FastAPI** сделает за вас большую часть работы.

/// info | Информация

Если вы уже прошли всё руководство и вернулись, чтобы узнать больше о типах, хорошим ресурсом будет <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">«шпаргалка» от `mypy`</a>.

///
