# Вступ до типів 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 and 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='також називають «побітовим "або" оператором», але це значення тут не актуальне'>вертикальної смуги (`|`)</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+ alternative

```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 types** or **Generics**, наприклад:

//// tab | Python 3.10+

Ви можете використовувати ті самі вбудовані типи як generic (з квадратними дужками та типами всередині):

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

І так само, як і в попередніх версіях Python, з модуля `typing`:

* `Union`
* `Optional`
* ...та інші.

У Python 3.10 як альтернативу використанню generic `Union` та `Optional` ви можете використовувати <abbr title='також називають «побітовим "або" оператором», але це значення тут не актуальне'>вертикальну смугу (`|`)</abbr> для оголошення об’єднань типів — це значно краще й простіше.

////

//// tab | Python 3.9+

Ви можете використовувати ті самі вбудовані типи як generic (з квадратними дужками та типами всередині):

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

І generic з модуля `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 - User Guide](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** використовує ті самі оголошення для:

* **Визначення вимог**: з параметрів шляху запиту, параметрів запиту, заголовків, тіл, залежностей тощо.
* **Перетворення даних**: із запиту в необхідний тип.
* **Перевірки даних**: що надходять від кожного запиту:
    * Генерування **автоматичних помилок**, що повертаються клієнту, коли дані недійсні.
* **Документування** API за допомогою OpenAPI:
    * який потім використовується для автоматичної інтерактивної документації користувальницьких інтерфейсів.

Все це може здатися абстрактним. Не хвилюйтеся. Ви побачите все це в дії в [Tutorial - User Guide](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>.

///
