Dev48
ЯЗЫК
  • О нас
  • Услуги
  • Индустрии
  • Технологии
  • Статьи
  • Контакты
Забронировать звонок
    Главная/Статьи/Pep 849 bolee vyrazitelnye tipovye vyrazheniya
Dev48

© 2026 · All rights reserved.

PEP 849: Более выразительные типовые выражения

Источник: Python Enhancement Proposals (PEPs)

PEP 849: Более выразительные типовые выражения

Источник: Python Enhancement Proposals (PEPs)

В настоящее время интерпретатор Python позволяет использовать любое выражение в качестве аннотации. Но наиболее популярное их применение, типовые аннотации, ограничены только небольшим подмножеством возможных выражений. Это связано с тем, что типовые аннотации должны быть интроспектируемыми во…

27 сентября 2026 г.•Обновлено: 27 сентября 2026 г.
  • Аннотация
  • Мотивация
  • Обзор обоснования Реализация Влияние на производительность Использование за пределами аннотаций
  • Обзор
  • Реализация
  • Влияние на производительность
  • Использование за пределами аннотаций
  • Спецификация Формат AST Вспомогательные функции
  • Формат AST
  • Вспомогательные функции
  • Обратная совместимость
  • Влияние на безопасность
  • Как этому научить
  • Эталонная реализация
  • Отвергнутые идеи Создание объектов типов внутри функций аннотирования Сохранение исходного кода аннотаций Невозврат пространств имен AST
  • Создание объектов типов внутри функций аннотирования
  • Сохранение исходного кода аннотаций
  • Невозврат пространств имен AST

Аннотация

В настоящее время интерпретатор Python позволяет использовать любое выражение в качестве аннотации. Но их наиболее популярное применение — аннотации типов — ограничено лишь небольшим подмножеством возможных выражений. Это связано с тем, что аннотации типов должны быть доступны для интроспекции во время выполнения, а многие допустимые выражения производят значения, для которых это невозможно. Это сильно ограничивает выразительность и простоту использования аннотаций типов, а также те выражения, которым может быть назначен смысл в спецификации типизации.

Данный PEP предлагает расширить функции аннотирования таким образом, чтобы в аннотациях типов можно было использовать любое выражение Python. Это достигается путем создания нового формата аннотаций, который предписывает функциям аннотирования возвращать AST (абстрактное синтаксическое дерево) и пространства имен аннотации. Потребители аннотаций типов во время выполнения могут затем вычислять их в объекты типизации.

Мотивация

Аннотации типов в Python — это выражения, привязанные к именам переменных, которые обозначают, какие значения могут быть присвоены переменной. Они доступны не только линтерам и средствам проверки типов напрямую через исходный код, но и для использования в механизмах отражения во время выполнения (runtime reflection), поскольку интерпретатор создает специальные функции аннотирования, вычисляющие объекты, в которые оцениваются выражения аннотаций.

Хотя этот подход хорошо работает для многих существующих аннотаций и прост для понимания, он также сильно ограничивает виды выражений, которые можно использовать в качестве аннотаций типов. Абстрактно говоря, это происходит потому, что функции аннотирования вычисляют такие выражения с использованием того же механизма, который Python использует для обычных выражений значений, и во многих случаях эта семантика несовместима с потребностями отражения во время выполнения.

Теперь мы приведем несколько более конкретных примеров выражений, которые могли бы найти применение в аннотациях типов, но в настоящее время неосуществимы. Тем не менее, мы не призываем напрямую ни к какому конкретному такому использованию, и этот PEP не реализует ни одно из них. Скорее, мы закладываем основу, которая сделает возможными их и подобные будущие предложения.

PEP 586 представил литеральные типы, которые перечисляют конкретный список возможных литеральных значений, например, числа 1, 2 и 3. В настоящее время это записывается как Literal[1, 2, 3. Многие пользователи интуитивно хотят вместо этого писать 1 | 2 | 3, используя голые литералы и оператор объединения для их соединения, что также используется в других языках, таких как TypeScript. В настоящее время это невозможно в Python, потому что выражение 1 | 2 | 3 вычисляется просто в 3, так как | интерпретируется как операция побитового ИЛИ, а не как объединение типов. Кроме того, свертка констант исключает появление этого выражения в сгенерированном байт-коде, и поэтому невозможно получить фактическую аннотацию типа во время выполнения.

Приведенный выше пример лишь мешает нам использовать чуть более короткие варианты написания уже существующих типов. Существует также множество аннотаций типов, которые либо вполне возможны, либо даже обсуждаются в настоящее время, но которые трудно реализовать в данный момент. Текущий открытый проект PEP 827 представляет множество таких типов. Например, он предлагает условные типы, которые представляют собой выражения типов, вычисляющиеся в один из двух типов в зависимости от некоторого условия. Интуитивно они записывались бы как FirstType if TypeCondition else OtherType, точно так же, как тернарные выражения. Но это невозможно, потому что независимо от того, во что оценивается TypeCondition во время выполнения, одно из других выражений типов никогда не будет вычислено и, следовательно, будет невидимо для интроспекции во время выполнения.

Подобные проблемы возникают при определении других типов, определяемых с использованием других существующих типов. Например, можно захотеть написать {K: NotRequired[T] for K, T in SomeTypedDict}, чтобы определить типизированный словарь, который имеет то же определение, что и существующий типизированный словарь, но в котором каждый ключ является необязательным. В настоящее время это невозможно, потому что по объектам типов нельзя итерироваться таким образом. Это также уже обсуждалось, например, в этой ветке.

Обоснование

Обзор

Мы предлагаем сделать возможным использование произвольных выражений в выражениях типов при сохранении полной возможности интроспекции во время выполнения путем внедрения нового формата для функций аннотирования. Он предпишет им возвращать объекты, содержащие как AST аннотаций, так и их пространство имен. Затем их можно использовать для построения фактических объектов типов, которые интересуют пользователей интроспекции во время выполнения.

Например, рассмотрим следующий класс:

При вызове MyClass.__annotate__(Format.VALUE) он по-прежнему будет возвращать обычный словарь аннотаций {"a": int, "b": list[str]}. Но при вызове как MyClass.__annotate__(Format.AST) мы получаем этот словарь:

Тем не менее, большинство пользователей никогда не увидят эти объекты напрямую. Вместо этого мы предлагаем добавить новую функцию get_type_annotations в модуль typing, которая будет внутренне выполнять вышеуказанный вызов, а затем возвращать привычный словарь аннотаций {"a": int, "b": list[str]}.

Сила этого подхода заключается в том, что он позволяет нам реализовывать новые выражения типов, подобные упомянутым выше, простым расширением логики вычислений в get_type_annotations. Эту логику затем можно полностью отделить от обычной семантики выражений Python и вместо этого создавать объекты типизации, обеспечивающие полную интроспекцию во время выполнения.

Мы также предлагаем добавить некоторую дополнительную вспомогательную функциональность, связанную с функциями аннотирования и этими объектами AST. В частности, новую функцию create_annoate_function в модуле annotationlib для легкого синтеза функции аннотирования. Основная часть этой функциональности уже реализована в модуле dataclasses, и мы прогнозируем, что многим пользователям это понадобится для создания функций аннотирования, поддерживающих этот несколько более сложный формат.

Реализация

Хотя точный механизм, используемый функциями аннотирования, является деталью реализации, может быть полезно посмотреть, как он может выглядеть. Рассмотрим, например, класс из примера выше:

Согласно этому предложению, его функция аннотирования может вести себя аналогично этому коду на Python:

Процесс протекает в три основных этапа. Сначала он заполняет словарь имен, выполняя поиск обычных имен переменных. Это важно для обеспечения возможности последующей оценки возвращаемых объектов AST с использованием правильных привязок для каждой переменной. Также загружаются некоторые строковые константы, содержащие двоичные данные, определяющие AST каждой аннотации. Это компактный формат, который легко реализуется с помощью существующего функционала компилятора. Кроме того, он анализируется гораздо быстрее, чем прямое хранение исходного кода аннотаций. Затем функция annotate использует новый внутренний механизм для фактического построения необходимых объектов AST аннотаций.

Предлагаемая функция get_type_annotations будет вычислять объекты типов с помощью примерно такой функции:

Влияние на производительность

Хотя каждое новое нововведение следует оценивать с точки зрения его влияния на производительность и сложность, функции, связанные с аннотациями типов, заслуживают дополнительного внимания, поскольку аннотации типов являются полностью опциональной частью языка Python. Таким образом, нам необходимо рассмотреть три отдельные группы пользователей и влияние на них: пользователей, которые вообще не используют аннотации типов, пользователей, которые аннотируют свой код для статических анализаторов и/или линтеров, но не используют интроспекцию во время выполнения, и, наконец, пользователей, которые также вычисляют свои аннотации типов во время выполнения.

Для этого мы рассмотрели три метрики: время импорта модулей как без аннотаций типов, так и с ними, размер функций annotate в памяти и время фактического вычисления функций annotate. Время импорта наиболее важно для первой группы пользователей; на вторую группу также влияет объем памяти, занимаемый функциями annotate, а время вычисления функций annotate влияет только на последнюю группу пользователей.

Используя нашу эталонную реализацию, мы не обнаружили существенной разницы во времени импорта модулей, не использующих аннотации типов. Для модулей, использующих аннотации, импорт происходил умеренно быстрее, а объем занимаемой памяти был немного меньше при использовании предлагаемых функций annotate — в обоих случаях на несколько процентов. Но, к сожалению, время вычисления может значительно возрасти. В худшем случае, когда аннотации запрашиваются в формате значения и определено каждое используемое имя, увеличение составляет примерно в семь раз. Однако, когда некоторые имена не определены и приходится использовать форматы STRING или FORWARDREF, текущий подход также работает значительно медленнее и приводит к времени, сопоставимому с предлагаемыми функциями annotate.

Распространенной ситуацией, когда аннотации типов вычисляются, является случай, когда такие инструменты, как или аналогичные ORM-пакеты, анализируют определения классов или функций для синтеза дополнительного поведения. Для таких инструментов время создания, например, датакласса (dataclass) изменится в результате этого предложения. Но, как упоминалось выше, уже существует множество ситуаций, когда инспекция аннотаций занимает сопоставимое количество времени.

В целом, поскольку негативное влияние на производительность затрагивает наименьшую группу пользователей, которые также получают выгоду от ставших возможными благодаря этому новых аннотаций типов, мы считаем это оправданным компромиссом. Спецификация предлагаемого формата также достаточно открыта, чтобы в будущем можно было реализовать множество оптимизаций, если это будет сочтено необходимым. Для многих пользователей это предложение даже обеспечит небольшое увеличение производительности, поскольку их код никогда не вычисляет какие-либо функции annotate.

Использование за пределами аннотаций

Хотя данное предложение позволяет использовать будущие выражения типов в аннотациях, существуют и другие места, где пользователи хотят писать выражения типов. Например, в cast(<сложное выражение>, value). Поскольку интерпретатор не может отличить эти случаи от других вызовов функций, он не может сделать вывод о том, что следует использовать предложенный нами механизм.

Этой проблемы можно избежать, используя промежуточный псевдоним типа:

Это позволяет использовать любое новое выражение типа внутри <сложное выражение>, поскольку псевдонимы типов также реализуются с помощью функций annotate.

Хотя это решение представляет собой лишь обходной путь проблемы, более комплексное исправление потребовало бы добавления нового ключевого слова в язык Python, что, по нашему мнению, не является необходимым. К этому решению можно будет вернуться в будущем, если использование подобных промежуточных псевдонимов типов действительно создаст серьезные проблемы в реальном коде.

Спецификация

В рамках этого PEP, когда мы говорим о функциях/методах annotate, мы имеем в виду как специальные методы __annotate__, имеющиеся у некоторых объектов, так и следующие методы, имеющиеся у объектов typing:

  • evaluate_value в
  • evaluate_bound, evaluate_constraints и evaluate_default в
  • evaluate_default в
  • evaluate_default в

Говоря об их возвращаемых значениях, мы имеем в виду либо объекты, содержащиеся в словаре, возвращаемом методами __annotate__, либо единственный объект, возвращаемый другими методами.

Формат AST

В перечисление annotationlib.Format добавляется новое значение AST со значением 5. Функции annotate не обязаны поддерживать этот формат. Если функция annotate вызывается с этим форматом, она должна возвращать объект AnnotationAST. Это экземпляры предлагаемого нового класса, которые содержат AST аннотации в виде объекта ast.expr и пространство имен, используемое для их оценки.

Сгенерированные компилятором функции annotate всегда будут поддерживать этот формат. Они будут хранить необходимые данные AST в виде строковых констант, а затем конструировать новые объекты AnnotationAST каждый раз при вызове. Содержащиеся объекты AST будут идентичны объектам, создаваемым путем непосредственного синтаксического анализа исходного кода аннотации.

Вспомогательные функции

Добавляется новая функция typing.get_type_annotations, которая функционирует аналогично существующей annotationlib.get_annotations, но вместо этого вызывает базовую функцию annotate с Format.AST, а затем конструирует объекты typing с помощью новой вспомогательной функции typing.evaluate_type_ast.

Существующая функция typing.get_type_hints будет объявлена устаревшей. Она имеет немного другую семантику как по сравнению с annotationlib.get_annotations, так и по сравнению с предлагаемой функцией, что делает невозможным ее изменение для поддержки нового функционала. Она также окажется совершенно избыточной, поскольку пользователям аннотаций типов потребуется вызывать get_type_annotations вместо этого для правильного разрешения любых аннотаций типов, содержащих новые возможности typing. Оставление этой функции в текущем виде создаст лишь путаницу относительно того, какую функцию следует использовать.

Формат AST также предоставляет более простой и надежный метод создания аннотаций в форматах STRING и FORWARDREF. В настоящее время сгенерированные компилятором функции annotate не поддерживают их напрямую; вместо этого вспомогательные функции в пытаются создать AST с наилучшими усилиями, а затем декомпилируют его обратно в запрошенный формат. В рамках данного PEP эти вспомогательные функции будут вместо этого использовать формат AST и декомпилировать полученный результат. Во многих случаях это дает более точные результаты для данных форматов.

Чтобы упростить создание синтезированных функций аннотирования, в модуль annotationlib будет добавлена новая вспомогательная функция .create_annotate_function. Она принимает отображение имен переменных на объекты аннотаций в одном из существующих форматов аннотаций. Используя их, она возвращает функцию аннотирования, которая поддерживает все форматы аннотаций путем соответствующего преобразования переданных значений.

Обратная совместимость

Новый формат аннотаций и вспомогательные функции добавляют только новую функциональность и, следовательно, не вызывают проблем с обратной совместимостью. Устаревание функции typing.get_type_hints означает, что существующий код, использующий ее, перестанет работать после ее удаления из стандартной библиотеки. Мы рекомендуем пользователям перейти на annotationlib.get_annotations или typing.get_type_annotations, в зависимости от того, какую семантику они хотят использовать. Хотя в некоторых случаях эти функции имеют немного разное поведение, в большинстве случаев они являются прямой заменой.

Мы также хотим отметить, что будущие дополнения, подобные тем, что описаны в разделе «Мотивация», не создают проблем с обратной совместимостью, даже если внедряются постепенно.

Рассмотрим, например, изменение в спецификации типов, в результате которого var: 1 начинает интерпретироваться как литеральный тип Literal[1]. Пользователи, вычисляющие метод аннотации напрямую или с помощью одного из существующих вспомогательных методов, по-прежнему будут получать результат в виде обычного целого числа 1. Измененная семантика учитывается только тогда, когда пользователь явно выбирает ее, вызывая функцию аннотации с Format.AST или используя typing.get_type_annotations.

В наиболее распространенном варианте использования аннотации потребляются не самим пользователем, а некоторым кодом библиотеки, который выполняет интроспекцию определенных пользователем объектов. Таким образом, внедрение новой семантики аннотаций может застопориться, если авторам библиотек приходится беспокоиться о том, что существующие аннотации пользователей будут переинтерпретированы в другие объекты при обновлении библиотеки. Но это тоже не является проблемой. Спецификация типов уже охвачена вопросами обратной совместимости, что означает, что любые действующие в настоящее время аннотации типов не будут изменены так, чтобы означать что-то другое. Возможные будущие изменения могут определять новую семантику только для конструкций синтаксиса, которые в настоящее время не являются допустимыми аннотациями типов и, следовательно, не встречаются в коде пользователя.

Влияние на безопасность

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

Как этому научить

Пользователи аннотаций напрямую не затронуты этим предложением. Оно призвано упростить написание аннотаций типов; любые будущие изменения, основанные на этом PEP, должны оцениваться по их собственным достоинствам. Документация проинформирует пользователей о том, что любая новая семантика поддерживается нативно только в аннотациях. Поскольку это на сегодняшний день самое распространенное место для написания типов, мы ожидаем, что это не будет большим ограничением. Другие места, где могут встречаться типы, уже требуют от пользователей оборачивать отложенные ссылки в строковые литералы, так что это устоявшаяся практика. Средства проверки типов должны предупреждать пользователей, если они не оборачивают форму типа с использованием синтаксиса, который будет вычислен некорректно в месте, где статически известно о необходимости формы типа.

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

Одной из потенциальных проблем является «Использование вне аннотаций», описанное в начале. Поскольку большинство пользователей аннотаций типов также используют средства проверки типов и/или линтеры, мы рекомендуем этим инструментам реализовать проверки на данные ошибки и предлагать исправление через промежуточный псевдоним типа.

Эталонная реализация

Это предложение реализовано в виде прототипа в форке CPython.

Отвергнутые идеи

Создание объектов типов внутри функций аннотирования

Первоначальная идея заключалась в том, чтобы изменить функции аннотирования так, чтобы они сами создавали соответствующие объекты типов. Этого можно достичь несколькими способами, например, с помощью нового необязательного аргумента для сигнализации семантики, специфичной для типов, или нового формата. Эти подходы были отвергнуты, поскольку они заставляют определять семантику, специфичную для типов, в самом интерпретаторе. Это не только ограничивает семантику (например, требованием отсутствия поиска по пространствам имен), но и привязывает использование возможностей типов к используемой версии Python, вместо того чтобы разрешать потенциальное портирование назад (backporting) с помощью typing_extensions.

Хранение исходного кода аннотаций

Вместо хранения бинарных данных, определяющих AST аннотаций, альтернативой является простое хранение исходного кода аннотаций напрямую. Это также представлялось как возможность еще в PEP 649. Два подхода в значительной степени эквивалентны, поскольку можно создать AST из исходного кода и наоборот. Хотя неразобранное AST не обязательно является точной строкой, которая была в исходном коде, поскольку AST не сохраняет точное форматирование кода, оно семантически эквивалентно и, в частности, не подвержено оптимизациям компилятора.

При сравнении производительности эти два представления данных также в значительной степени эквивалентны с точки зрения времени импорта и использования памяти. Однако синтаксический анализ исходного кода с последующим созданием объектов типов работает значительно медленнее (в 3 раза), чем работа с данными AST. Хранение исходного кода также сильно ограничивает будущие оптимизации, поскольку представление данных напрямую предоставляется в качестве API.

Невозврат пространств имен AST

Чтобы корректно вычислить AST в правильные объекты типов, функция вычисления должна иметь доступ к пространству имен, в котором была определена аннотация. Сначала кажется, что это пространство имен можно восстановить из объекта функции аннотирования, поскольку они содержат используемые globals и cellvars. Однако этого недостаточно, так как пространства имен могут использовать более сложную логику поиска при использовании инструкций global или искажения имен (name mangling).

Этот документ передан в общественное достояние или под лицензию CC0-1.0-Universal, в зависимости от того, какая из них более мягкая.

← Все статьи

Ещё в разделе «Разработка ПО»

Все →
Обзор Sennheiser Momentum 5: великолепный звук, невероятное время автономной работы и минимум компромиссовПресса
Momentum

Обзор Sennheiser Momentum 5: великолепный звук, невероятное время автономной работы и минимум компромиссов

Boeing сообщает о программном сбое в 737 Max, затрагивающем некоторые функции автоматического захода на посадкуПресса
Boeing

Boeing сообщает о программном сбое в 737 Max, затрагивающем некоторые функции автоматического захода на посадку

Apple обязали выплатить 5,7 млрд долларов по иску о нарушении патентных прав на тактильные технологии в iPhone и Apple Watch
Пресса
Apple

Apple обязали выплатить 5,7 млрд долларов по иску о нарушении патентных прав на тактильные технологии в iPhone и Apple Watch

Компании выбирают САПР от PTC для разработки и проектирования продуктов
PTC

Компании выбирают САПР от PTC для разработки и проектирования продуктов

Clas Ohlson выбирает PTC FlexPLM для поддержки своей стратегии роста
PTC

Clas Ohlson выбирает PTC FlexPLM для поддержки своей стратегии роста

Canon ITS и PTC Japan запускают «Smart PLM Support Service» для поддержки трансформации рабочих процессов в сфере производства
PTC

Canon ITS и PTC Japan запускают «Smart PLM Support Service» для поддержки трансформации рабочих процессов в сфере производства

Ещё от Python

PEP 823: Операторы доступа с проверкой на None
Python

PEP 823: Операторы доступа с проверкой на None

PEP 824: None-coalescing operators
Python

PEP 824: None-coalescing operators

PEP 848: Поколенческая инкрементальная сборка мусора
Python

PEP 848: Поколенческая инкрементальная сборка мусора

PEP 846: Строки документации (docstrings) для псевдонимов типов
Python

PEP 846: Строки документации (docstrings) для псевдонимов типов

Python SDK против Coded Agents и Studio Web: выбор из трех вариантов, о котором вы даже не подозревали
Python

Python SDK против Coded Agents и Studio Web: выбор из трех вариантов, о котором вы даже не подозревали