typing.Annotated в Python

02 октября 2026
Автор

Антон Ларичев

Что такое typing.Annotated

typing.Annotated — это специальная форма из модуля typing, появившаяся в Python 3.9 (PEP 593). Она позволяет прикрепить к аннотации типа произвольные метаданные, не изменяя семантику самого типа с точки зрения статических анализаторов.

До появления Annotated единственным способом передать дополнительную информацию вместе с типом были комментарии или отдельные словари конфигурации. Теперь метаданные живут прямо рядом с аннотацией и доступны во время исполнения через typing.get_type_hints.

Курс по теме

Основы Python — курс

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Синтаксис

Базовая форма выглядит так:

from typing import Annotated

Annotated[тип, метаданные_1, метаданные_2, ...]

Первый аргумент — это сам тип. Все последующие аргументы — произвольные объекты, которые несут дополнительный смысл для инструментов, библиотек или вашего собственного кода.

Простой пример:

from typing import Annotated

def process_age(age: Annotated[int, "must be positive"]) -> None:
    print(age)

Статический анализатор (mypy, pyright) видит здесь просто int. Строка "must be positive" полностью игнорируется с точки зрения проверки типов, но доступна во время выполнения.

Доступ к метаданным во время выполнения

Метаданные хранятся в атрибуте __metadata__ объекта Annotated, а сам тип — в __origin__ и __args__.

from typing import Annotated, get_type_hints, get_args, get_origin

def greet(name: Annotated[str, "non-empty", "max 50 chars"]) -> str:
    return f"Hello, {name}"

hints = get_type_hints(greet, include_extras=True)
print(hints["name"])
# Annotated[str, 'non-empty', 'max 50 chars']

annotated_type = hints["name"]
print(get_args(annotated_type))
# (str, 'non-empty', 'max 50 chars')

print(annotated_type.__metadata__)
# ('non-empty', 'max 50 chars')

Важная деталь: get_type_hints без флага include_extras=True удаляет Annotated-обёртку и возвращает голый тип. Передавайте этот флаг, когда метаданные нужны во время выполнения.

Создание собственных валидаторов

Рассмотрим практический пример — написание простого декоратора, который читает метаданные из аннотаций и проверяет аргументы функции.

from typing import Annotated, get_type_hints, get_args
from dataclasses import dataclass
import functools

@dataclass
class Gt:
    value: float

@dataclass
class Lt:
    value: float

@dataclass
class MaxLen:
    value: int

def validate(func):
    hints = get_type_hints(func, include_extras=True)

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        sig_params = list(func.__code__.co_varnames[:func.__code__.co_argcount])
        bound = dict(zip(sig_params, args))
        bound.update(kwargs)

        for param_name, annotated in hints.items():
            if param_name == "return":
                continue
            if not hasattr(annotated, "__metadata__"):
                continue
            value = bound.get(param_name)
            for meta in annotated.__metadata__:
                if isinstance(meta, Gt) and value <= meta.value:
                    raise ValueError(f"{param_name} must be > {meta.value}, got {value}")
                if isinstance(meta, Lt) and value >= meta.value:
                    raise ValueError(f"{param_name} must be < {meta.value}, got {value}")
                if isinstance(meta, MaxLen) and len(value) > meta.value:
                    raise ValueError(f"{param_name} length must be <= {meta.value}")

        return func(*args, **kwargs)

    return wrapper

@validate
def create_user(
    name: Annotated[str, MaxLen(50)],
    age: Annotated[int, Gt(0), Lt(150)],
) -> dict:
    return {"name": name, "age": age}

print(create_user("Alice", 30))
# {'name': 'Alice', 'age': 30}

create_user("Bob", -5)
# ValueError: age must be > 0, got -5

Этот паттерн — основа того, как работают Pydantic и FastAPI.

Использование в Pydantic

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

from typing import Annotated
from pydantic import BaseModel, Field
from pydantic.functional_validators import AfterValidator

def check_positive(v: int) -> int:
    if v <= 0:
        raise ValueError("must be positive")
    return v

PositiveInt = Annotated[int, AfterValidator(check_positive)]

class Product(BaseModel):
    name: Annotated[str, Field(min_length=1, max_length=100)]
    price: Annotated[float, Field(gt=0)]
    quantity: PositiveInt

p = Product(name="Widget", price=9.99, quantity=5)
print(p)
# name='Widget' price=9.99 quantity=5

Product(name="", price=9.99, quantity=5)
# ValidationError: name must have at least 1 character

Обратите внимание на PositiveInt — это псевдоним типа, созданный через Annotated. Его можно переиспользовать в нескольких моделях, избегая дублирования правил валидации.

Использование в FastAPI

FastAPI использует Annotated для описания источников параметров запроса прямо в сигнатуре функции-обработчика.

from typing import Annotated
from fastapi import FastAPI, Query, Path, Body
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

@app.get("/items/{item_id}")
async def get_item(
    item_id: Annotated[int, Path(ge=1, description="ID товара")],
    q: Annotated[str | None, Query(max_length=50)] = None,
) -> dict:
    return {"item_id": item_id, "q": q}

@app.post("/items/")
async def create_item(
    item: Annotated[Item, Body(embed=True)],
) -> Item:
    return item

Благодаря Annotated конфигурация параметра (источник, ограничения, описание) находится рядом с самой аннотацией, а не вынесена в значение по умолчанию. Это делает сигнатуру функции более читаемой и даёт FastAPI полную информацию для автоматической генерации OpenAPI-схемы.

Создание переиспользуемых типов-псевдонимов

Одно из главных преимуществ Annotated — возможность создавать именованные типы с уже встроенными ограничениями.

from typing import Annotated
from pydantic import Field

# Переиспользуемые типы
Username = Annotated[str, Field(min_length=3, max_length=30, pattern=r"^[a-zA-Z0-9_]+$")]
Email = Annotated[str, Field(pattern=r"^[\w.-]+@[\w.-]+\.\w+$")]
PositiveFloat = Annotated[float, Field(gt=0)]
Percentage = Annotated[float, Field(ge=0.0, le=100.0)]

from pydantic import BaseModel

class User(BaseModel):
    username: Username
    email: Email
    score: Percentage

class Product(BaseModel):
    sku: Username  # тот же тип переиспользуется
    discount: Percentage
    price: PositiveFloat

Такой подход централизует правила валидации: изменив Username в одном месте, вы автоматически обновляете правила во всех моделях.

Annotated и TypeVar

Annotated корректно работает в сочетании с TypeVar, что позволяет создавать обобщённые аннотированные типы.

from typing import Annotated, TypeVar, Generic

T = TypeVar("T")

class Positive:
    """Маркер: значение должно быть положительным."""

PositiveValue = Annotated[T, Positive()]

def double(x: PositiveValue[int]) -> int:
    return x * 2

print(double(5))   # 10
print(double(-1))  # работает без валидации — нужен отдельный декоратор

Здесь PositiveValue[int] разворачивается в Annotated[int, Positive()]. Это полезно при написании библиотечного кода, где конкретный тип заранее неизвестен.

Вложенность и порядок метаданных

Аннотации Annotated можно вкладывать друг в друга — при этом метаданные объединяются слева направо.

from typing import Annotated

Base = Annotated[int, "positive"]
Extended = Annotated[Base, "less than 100"]

print(Extended.__metadata__)
# ('positive', 'less than 100')

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

Взаимодействие с dataclasses

Annotated отлично работает со стандартными датаклассами Python. Метаданные можно читать через fields из dataclasses.

from typing import Annotated
from dataclasses import dataclass, fields

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

@dataclass
class Measurement:
    temperature: Annotated[float, Unit("celsius")]
    pressure: Annotated[float, Unit("pascal")]
    label: str

import typing

hints = typing.get_type_hints(Measurement, include_extras=True)
for field_name, hint in hints.items():
    if hasattr(hint, "__metadata__"):
        for meta in hint.__metadata__:
            if isinstance(meta, Unit):
                print(f"{field_name}: единица измерения = {meta.name}")

# temperature: единица измерения = celsius
# pressure: единица измерения = pascal

Отличие от NewType

Часто возникает вопрос: когда использовать Annotated, а когда — NewType?

NewType создаёт отдельный тип в системе типов: статический анализатор не позволит передать UserId туда, где ожидается int, и наоборот. Это жёсткое разграничение.

from typing import NewType

UserId = NewType("UserId", int)

def get_user(user_id: UserId) -> dict:
    return {}

get_user(42)        # mypy: ошибка — ожидается UserId, не int
get_user(UserId(42))  # корректно

Annotated не создаёт нового типа — с точки зрения статического анализатора Annotated[int, ...] это всё тот же int. Метаданные несут смысл только для инструментов, которые умеют их читать.

Выбор прост:

  • Нужна изоляция на уровне системы типов — используйте NewType.
  • Нужны метаданные для валидации, документации или другой обработки во время выполнения — используйте Annotated.

Совместимость с Python 3.8

Если проект поддерживает Python 3.8, импортируйте Annotated из typing_extensions:

try:
    from typing import Annotated  # Python 3.9+
except ImportError:
    from typing_extensions import Annotated  # Python 3.8

Библиотека typing_extensions доступна через pip и содержит бэкпорты новых возможностей системы типов Python.

Итоговые рекомендации

Используйте typing.Annotated, когда:

  • Хотите добавить правила валидации прямо к аннотации поля в Pydantic или FastAPI.
  • Создаёте переиспользуемые типы-псевдонимы с встроенными ограничениями.
  • Пишете библиотечный код, которому нужно читать метаданные аннотаций во время выполнения.
  • Хотите хранить документирующую информацию (единицы измерения, источник данных, права доступа) рядом с самой аннотацией.

Не используйте Annotated как замену нормальной валидации на уровне кода или как способ обойти систему типов — метаданные действуют только тогда, когда вызывающий код явно их проверяет.

Подробнее о системе типов Python и работе с Pydantic и FastAPI вы можете узнать на курсе PurpleSchool: https://purpleschool.ru/course/python?utm_source=knowledgebase&utm_medium=text&utm_campaign=python-typing-annotated

Стрелочка влевоОператор моржа := в PythonАннотации типов (Type Hints) в PythonСтрелочка вправо

Постройте личный план изучения Python до уровня Middle — бесплатно!

Python — часть карты развития Backend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Все гайды по Python

Как отправлять запросы с помощью requests в PythonПочему Python выводит значение без команды printВозможности Python для автоматизации задачРабота с JSON в Python на примерахКак работает команда print в PythonPython get — методы получения данныхКак находить и исправлять ошибки в PythonРабота с данными через API и внешние сервисыСтруктура и оформление кода PythonОсновы Django с PythonПолезные приёмы в Python для повседневной работыИспользование locals в Python для отладкиКак выполнять HTTPS-запросы в PythonFastAPI Python — быстрый старт: создание REST API с нуляКак работать с API в PythonИнтеграция PHP и Python
Ввод целого числа в PythonВедение логов в PythonУдаление данных в Python с помощью removeОбработка исключений с помощью try/except в PythonФункция super() в Python — как вызвать метод родителяСоздание собственных контекстных менеджеров в PythonРабота с символами программирования PythonРабота с переменной X в PythonРабота с классами в PythonКак скачать Python на компьютерПростая программа на Python для начинающихЧто нового в Python 3Поддерживается ли Python 2 и стоит ли его использоватьPython 1 — с чего начиналась история языкаОсновы Python для тех, кто начинаетКоманда python print - полное руководство по выводу данныхПравила именования переменных в PythonПользовательские исключения в PythonОсновы Python coreОписание объектов PythonНаследование классов в Python — основы и примерыМножественное наследование в Python — примеры и MROКонтекстный менеджер with в Python — как работает и зачем нуженКомментарии в Python — однострочные, многострочные и docstringКакой Python выбрать для установкиКак вывести целое число с помощью print в PythonКак установить Python на Windows macOS и LinuxКак пользоваться консолью PythonКак получить последний элемент в PythonКак найти значение в PythonКак настроить PythonКак использовать print для строк в PythonКак работает интерпретатор PythonИнструкция по работе с PythonБлок finally в обработке исключений PythonЦелые числа в PythonАбстрактные классы в Python — ABC и abstractmethod
Загрузка данных PythonУправление проектами на GitHub с PythonСоздание веб-приложений на Flask PythonСоздание бота на PythonСоздание интерфейсов Python QTСоздание игр с PygameСоздание GUI в PythonКак работать со словарями в PythonРабота с библиотеками через Python PackagingРабота со временем в Python при помощи модуля timePython name — особенности переменнойМатематические операции в Python с модулем mathPytest — тестирование на Python: полное руководствоОбработка изображений с OpenCV PythonPython listing — что это и как использоватьNumPy в Python — основы и применение в задачахМашинное обучение с PythonИспользование Anaconda с PythonМодуль contextlib в Python — утилиты для контекстных менеджеровБиблиотеки Python и их применение в проектах
Возврат значений из функции в PythonВложенные функции в PythonСоздание собственных декораторов в PythonРабота с функцией map в PythonЦикл while в Python и примеры использованияОбработка чисел, введённых через input в PythonОсновные операторы в Python с примерамиУсловные выражения if else в Python для начинающихКак выполняется вызов функций call в PythonПродвинутые генераторы в Python — send, throw, close и корутиныПозиционные и именованные аргументы в PythonОбъявление переменных и управление областью видимости в PythonПередача аргументов по ссылке и по значению в PythonПередача аргументов через args и kwargs в PythonОсновные методы Python и примеры их использованияОператор match/case в Python 3.10+ — основы структурного сопоставленияПаттерны match/case в Python — деструктуризация, guard и вложенные шаблоныПрактические примеры match/case в Python — реальные сценарии примененияЛокальные и глобальные переменные в PythonЧасто используемые команды PythonКлючевые слова global и nonlocal в PythonКак создавать функции в PythonКак работает сборщик мусора в PythonКак работает область видимости переменных в PythonКак работает функция callable в PythonКак работает функция any и all в PythonКак проверить тип переменной в PythonКак передать функцию как аргумент в PythonКак использовать функцию isinstance в PythonКак использовать функцию filter в PythonКак использовать функцию filter в PythonКак использовать функцию eval безопасно в PythonКак использовать декораторы в PythonИзменяемые и неизменяемые типы данных в PythonГенераторы и yield в Python — как создавать и использоватьГенераторные выражения в Python — синтаксис и примерыФункции в Python и способы их вызоваФункции как объекты в PythonЧто такое замыкания в PythonЧто делает функция reduce в PythonЧто делает функция id в PythonАргументы по умолчанию в PythonАнонимные функции и lambda в PythonАлгоритмы на Python — примеры и объяснение
Запись данных в PythonУстановка pip в PythonУправление зависимостями requirement в PythonУправление библиотеками с помощью Python PackagingУдаление пробелов с помощью strip в PythonСтруктурирование кода в PythonСоздание исполняемого файла Python в exeРазбор traceback в модуле PythonРазбор site-packages в PythonРазбор Program Files в PythonРабота с Unicode кодировками в PythonРабота с системными функциями Python sysРабота с папкой AppData в PythonРабота с модулем logging в PythonРабота с каталогами в PythonРабота с CSV в PythonВиртуальная среда venv в Python — создание и настройкаКак создать простое приложение на PythonИспользование pip в Python для установки пакетовМодули в Python и организация кода в проектеИмпорт модулей в Python и правила подключенияРабота с файлами в Python пошаговоПолучение строки из модуля PythonЧто делает компилятор Python и как он работаетПодключение файлов в Python с includeПеременные среды в PythonСборка проекта с помощью packaging в PythonНастройка Python сервераИспользование Python на UbuntuИспользование консоли PythonИспользование кодировок в PythonИнициализация пакетов PythonИмпорт модулей PythonИмпорт имен в PythonСреда IDLE Python и базовые возможностиЧтение файлов в Python с помощью open fileЧтение и запись TXT в Python
Удаление элементов из списка PythonТипы данных в Python — обзор и рекомендацииОсновные операции со строками в PythonМетоды str в Python и обработка текстаСписки в Python и их ключевые методыСоздание списков данных в PythonРабота со строками и символами в PythonРабота со столбцами в PythonРабота со списком значений в PythonРабота с таблицами в Python с помощью DataFrameРабота с RFR в PythonРабота с пробелами в PythonРабота с массивами в PythonРабота с кортежами tuple PythonРабота с координатами X и Y в PythonРабота с ключами в PythonРабота с элементами данных PythonРабота с двоичными числами PythonРабота с данными в PythonРабота с данными NumPy PythonРабота с большими числами в PythonРабота с битами в PythonРабота с байтами в PythonЧто такое значение в Python и как его определитьМножества в Python и операции с нимиИспользование range в Python для цикловПроверка на четность в PythonПроверка числа в PythonПреобразование типов в PythonПреобразование числа в строку в PythonПреобразование списка в строку PythonПостроение графиков в PythonОпределение индекса элемента в PythonОкругление чисел в PythonОбъединение списков в Python с помощью zipМножества в PythonМассивы в Python и отличие от списковМассив чисел в PythonКортежи данных в PythonКак вычислить сумму чисел в PythonКак получить остаток от деления в PythonКак найти следующее число в PythonИспользование Unicode в PythonТип int в Python и его особенностиИндекс списка в PythonФункции для работы со строками в PythonЭлементы Python и способы доступа к нимДоступ к элементам массива в PythonДеление чисел в PythonРабота с данными в Python на практикеКак работать с числами в Python
Открыть базу знаний

Лучшие курсы по теме

Иконка молнииНовый
изображение курса

Основы Python

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий