Перейти к содержанию

Введение в типы Python

🌐 Перевод выполнен с помощью ИИ и людей

Этот перевод был сделан ИИ под руководством людей. 🤝

В нем могут быть ошибки из-за неправильного понимания оригинального смысла или неестественности и т. д. 🤖

Вы можете улучшить этот перевод, помогая нам лучше направлять ИИ LLM.

Английская версия

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

Эти «подсказки типов» или аннотации — это специальный синтаксис, позволяющий объявлять тип переменной.

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

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

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

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

Примечание

Если вы являетесь экспертом в 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"))
🤓 Other versions and variants
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"))
🤓 Other versions and variants
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"))
🤓 Other versions and variants
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
🤓 Other versions and variants
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
🤓 Other versions and variants
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_e
🤓 Other versions and variants
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_e

Модуль typing

Для некоторых дополнительных сценариев может понадобиться импортировать что-то из стандартного модуля typing. Например, когда вы хотите объявить, что что-то имеет «любой тип», можно использовать Any из typing:

from typing import Any


def some_function(data: Any):
    print(data)

Generic-типы

Некоторые типы могут принимать «параметры типов» в квадратных скобках, чтобы определить их внутренние типы. Например, «список строк» объявляется как list[str].

Такие типы, которые принимают параметры типов, называются Generic-типами или Generics.

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

  • list
  • tuple
  • set
  • dict

List

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

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

В качестве типа укажите 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)

Информация

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

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

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

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

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

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

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

Tuple и Set

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

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:

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 (скажем, цена каждой позиции).

Union

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

Чтобы это определить, используйте вертикальную черту (|) для разделения обоих типов.

Это называется «объединение» (union), потому что переменная может быть чем угодно из объединения этих двух множеств типов.

def process_item(item: int | str):
    print(item)

Это означает, что item может быть int или str.

Возможно None

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

def say_hi(name: str | None = None):
    if name is not None:
        print(f"Hey {name}!")
    else:
        print("Hello World")

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

Классы как типы

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

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

class Person:
    def __init__(self, name: str):
        self.name = name


def get_person_name(one_person: Person):
    return one_person.name
🤓 Other versions and variants
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
🤓 Other versions and variants
class Person:
    def __init__(self, name: str):
        self.name = name


def get_person_name(one_person: Person):
    return one_person.name

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

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

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

Pydantic-модели

Pydantic — это библиотека Python для валидации данных.

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

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

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

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

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

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

Информация

Чтобы узнать больше о Pydantic, ознакомьтесь с его документацией.

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

Вы увидите намного больше всего этого на практике в Учебник - Руководство пользователя.

Подсказки типов с аннотациями метаданных

В Python также есть возможность добавлять дополнительные метаданные к подсказкам типов с помощью Annotated.

Вы можете импортировать Annotated из typing.

from typing import Annotated


def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
    return f"Hello {name}"
🤓 Other versions and variants
from typing import Annotated


def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
    return f"Hello {name}"

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

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

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

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

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

Совет

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

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

Аннотации типов в FastAPI

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

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

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

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

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

Всё это может звучать абстрактно. Не волнуйтесь. Вы увидите всё это в действии в Учебник - Руководство пользователя.

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

Информация

Если вы уже прошли всё руководство и вернулись, чтобы узнать больше о типах, хорошим ресурсом будет «шпаргалка» от mypy.