Перейти до змісту

Вступ до типів 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.8, із модуля typing:

  • Union
  • Optional
  • ...та інші.

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

  • list
  • tuple
  • set
  • dict

І те саме, що й у Python 3.8, із модуля typing:

  • Union
  • Optional (так само як у Python 3.8)
  • ...та інші.

У 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.