Вступ до типів Python¶
Python підтримує додаткові "підказки типу" ("type hints") (також звані "анотаціями типу" ("type annotations")).
Ці "type hints" є спеціальним синтаксисом, що дозволяє оголошувати тип змінної.
За допомогою оголошення типів для ваших змінних, редактори та інструменти можуть надати вам кращу підтримку.
Це просто швидкий посібник / нагадування про анотації типів у 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
Ось і все.
Це "type hints":
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
. І внутрішні значення також можуть мати свій тип.
Ці типи, які мають внутрішні типи, називаються "generic" типами. І оголосити їх можна навіть із внутрішніми типами.
Щоб оголосити ці типи та внутрішні типи, ви можете використовувати стандартний модуль Python typing
. Він існує спеціально для підтримки анотацій типів.
Новіші версії Python¶
Синтаксис із використанням typing
сумісний з усіма версіями, від Python 3.6 до останніх, включаючи Python 3.9, Python 3.10 тощо.
У міру розвитку Python новіші версії мають покращену підтримку анотацій типів і в багатьох випадках вам навіть не потрібно буде імпортувати та використовувати модуль typing
для оголошення анотацій типу.
Якщо ви можете вибрати новішу версію Python для свого проекту, ви зможете скористатися цією додатковою простотою. Дивіться кілька прикладів нижче.
List (список)¶
Наприклад, давайте визначимо змінну, яка буде list
із str
.
З модуля typing
, імпортуємо List
(з великої літери L
):
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
Оголосимо змінну з тим самим синтаксисом двокрапки (:
).
Як тип вкажемо List
, який ви імпортували з typing
.
Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки:
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
Оголосимо змінну з тим самим синтаксисом двокрапки (:
).
Як тип вкажемо list
.
Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки:
def process_items(items: list[str]):
for item in items:
print(item)
Info
Ці внутрішні типи в квадратних дужках називаються "параметрами типу".
У цьому випадку, str
це параметр типу переданий у List
(або list
у Python 3.9 і вище).
Це означає: "змінна items
це list
, і кожен з елементів у цьому списку - str
".
Tip
Якщо ви використовуєте Python 3.9 і вище, вам не потрібно імпортувати List
з typing
, ви можете використовувати натомість тип list
.
Зробивши це, ваш редактор може надати підтримку навіть під час обробки елементів зі списку:
Без типів цього майже неможливо досягти.
Зверніть увагу, що змінна item
є одним із елементів у списку items
.
І все ж редактор знає, що це str
, і надає підтримку для цього.
Tuple and 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
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)
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
(наприклад, ціна кожного елементу).
- Ключі цього
Union (об'єднання)¶
Ви можете оголосити, що змінна може бути будь-яким із кількох типів, наприклад, int
або str
.
У Python 3.6 і вище (включаючи Python 3.10) ви можете використовувати тип Union
з typing
і вставляти в квадратні дужки можливі типи, які можна прийняти.
У Python 3.10 також є альтернативний синтаксис, у якому ви можете розділити можливі типи за допомогою вертикальної смуги (|
).
from typing import Union
def process_item(item: Union[int, str]):
print(item)
def process_item(item: int | str):
print(item)
В обох випадках це означає, що item
може бути int
або str
.
Possibly None
(Optional)¶
Ви можете оголосити, що значення може мати тип, наприклад str
, але також може бути None
.
У Python 3.6 і вище (включаючи Python 3.10) ви можете оголосити його, імпортувавши та використовуючи Optional
з модуля typing
.
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
.
Optional[Something]
насправді є скороченням для Union[Something, None]
, вони еквівалентні.
Це також означає, що в Python 3.10 ви можете використовувати Something | 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")
from typing import Union
def say_hi(name: Union[str, None] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
def say_hi(name: str | None = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
Generic типи¶
Ці типи, які приймають параметри типу у квадратних дужках, називаються Generic types or Generics, наприклад:
List
Tuple
Set
Dict
Union
Optional
- ...та інші.
Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині):
list
tuple
set
dict
І те саме, що й у Python 3.6, із модуля typing
:
Union
Optional
- ...та інші.
Ви можете використовувати ті самі вбудовані типи, як generic (з квадратними дужками та типами всередині):
list
tuple
set
dict
І те саме, що й у Python 3.6, із модуля typing
:
Union
Optional
(так само як у Python 3.6)- ...та інші.
У Python 3.10, як альтернатива використанню Union
та Optional
, ви можете використовувати вертикальну смугу (|
) щоб оголосити об'єднання типів.
Класи як типи¶
Ви також можете оголосити клас як тип змінної.
Скажімо, у вас є клас Person
з імʼям:
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
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
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
Info
Щоб дізнатись більше про Pydantic, перегляньте його документацію.
FastAPI повністю базується на Pydantic.
Ви побачите набагато більше цього всього на практиці в Tutorial - User Guide.
Анотації типів у FastAPI¶
FastAPI використовує ці підказки для виконання кількох речей.
З FastAPI ви оголошуєте параметри з підказками типу, і отримуєте:
- Підтримку редактора.
- Перевірку типів.
...і FastAPI використовує ті самі оголошення для:
- Визначення вимог: з параметрів шляху запиту, параметрів запиту, заголовків, тіл, залежностей тощо.
- Перетворення даних: із запиту в необхідний тип.
- Перевірка даних: що надходять від кожного запиту:
- Генерування автоматичних помилок, що повертаються клієнту, коли дані недійсні.
- Документування API за допомогою OpenAPI:
- який потім використовується для автоматичної інтерактивної документації користувальницьких інтерфейсів.
Все це може здатися абстрактним. Не хвилюйтеся. Ви побачите все це в дії в Туторіал - Посібник користувача.
Важливо те, що за допомогою стандартних типів Python в одному місці (замість того, щоб додавати більше класів, декораторів тощо), FastAPI зробить багато роботи за вас.
Info
Якщо ви вже пройшли весь навчальний посібник і повернулися, щоб дізнатися більше про типи, ось хороший ресурс "шпаргалка" від mypy
.