8. Составные высказывания

Составные операторы содержат (группы) других операторов; они каким-то образом влияют на выполнение других операторов или управляют ими. Как правило, составные операторы занимают несколько строк, хотя в простых воплощениях весь составной оператор может содержаться в одной строке.

Операторы if, while и for реализуют традиционные конструкции потока управления. Оператор try задает обработчики исключений и/или код очистки для группы операторов, а оператор with позволяет выполнить код инициализации и финализации вокруг блока кода. Определения функций и классов также являются синтаксически составными операторами.

Составное высказывание состоит из одного или нескольких «пунктов». Клауза состоит из заголовка и «набора». Заголовки клаузул конкретного составного высказывания располагаются на одном уровне отступа. Заголовок каждой клаузы начинается с уникального идентифицирующего ключевого слова и заканчивается двоеточием. Набор - это группа утверждений, управляемых одним положением. Набор может состоять из одного или нескольких простых утверждений, разделенных точкой с запятой, расположенных на одной строке с заголовком, после двоеточия в заголовке, или из одного или нескольких утверждений с отступом на последующих строках. Только последняя форма набора может содержать вложенные составные утверждения; следующее является незаконным, в основном потому, что неясно, к какому if пункту относится следующий else пункт:

if test1: if test2: print(x)

Также обратите внимание, что точка с запятой в данном контексте связывается сильнее, чем двоеточие, так что в следующем примере будут выполнены либо все, либо ни один из вызовов print():

if x < y < z: print(x); print(y); print(z)

Подведение итогов:

compound_stmt ::=  if_stmt
                   | while_stmt
                   | for_stmt
                   | try_stmt
                   | with_stmt
                   | match_stmt
                   | funcdef
                   | classdef
                   | async_with_stmt
                   | async_for_stmt
                   | async_funcdef
suite         ::=  stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement     ::=  stmt_list NEWLINE | compound_stmt
stmt_list     ::=  simple_stmt (";" simple_stmt)* [";"]

Обратите внимание, что утверждения всегда заканчиваются символом NEWLINE, за которым может следовать DEDENT. Также обратите внимание, что необязательные предложения продолжения всегда начинаются с ключевого слова, которое не может начинать утверждение, поэтому не возникает никаких двусмысленностей (проблема «болтающихся else» решается в Python требованием делать отступ для вложенных if утверждений).

При форматировании грамматических правил в следующих разделах каждая клаузула вынесена на отдельную строку для наглядности.

8.1. Заявление if

Оператор if используется для условного выполнения:

if_stmt ::=  "if" assignment_expression ":" suite
             ("elif" assignment_expression ":" suite)*
             ["else" ":" suite]

Он выбирает ровно один из наборов, оценивая выражения по одному, пока одно из них не окажется истинным (определение true и false см. в разделе Булевы операции); тогда выполняется этот набор (и никакая другая часть оператора if не выполняется и не оценивается). Если все выражения ложны, то выполняется набор из оператора else, если он присутствует.

8.2. Заявление while

Оператор while используется для повторного выполнения до тех пор, пока выражение истинно:

while_stmt ::=  "while" assignment_expression ":" suite
                ["else" ":" suite]

При этом выражение многократно проверяется и, если оно истинно, выполняется первый набор; если выражение ложно (а это может быть первый раз, когда оно проверяется), выполняется набор из предложения else, если оно присутствует, и цикл завершается.

Оператор break, выполненный в первом наборе, завершает цикл, не выполняя набор предложений else. Оператор continue, выполненный в первом наборе, пропускает остальные наборы и возвращается к проверке выражения.

8.3. Заявление for

Оператор for используется для итерации по элементам последовательности (например, строки, кортежа или списка) или другого итерируемого объекта:

for_stmt ::=  "for" target_list "in" starred_list ":" suite
              ["else" ":" suite]

Выражение starred_list оценивается один раз; оно должно дать объект iterable. Для этого итератора создается iterator. Затем первый элемент, предоставленный итератором, присваивается целевому списку по стандартным правилам присваивания (см. Ведомости заданий), и выполняется набор. Это повторяется для каждого элемента, предоставленного итератором. Когда итератор исчерпан, выполняется набор в предложении else, если он присутствует, и цикл завершается.

Оператор break, выполненный в первом блоке, завершает цикл, не выполняя набор предложений else. Оператор continue, выполненный в первом сюите, пропускает остальные сюиты и продолжает работу со следующим элементом или с клаузулой else, если следующего элемента нет.

Цикл for выполняет присваивания переменным в целевом списке. При этом перезаписываются все предыдущие присваивания этим переменным, включая те, которые были сделаны в наборе цикла for:

for i in range(10):
    print(i)
    i = 5             # this will not affect the for-loop
                      # because i will be overwritten with the next
                      # index in the range

Имена в списке целей не удаляются после завершения цикла, но если последовательность пуста, то они вообще не будут присвоены циклом. Подсказка: встроенный тип range() представляет собой неизменяемые арифметические последовательности целых чисел. Например, при итерации range(3) последовательно получаются 0, 1, а затем 2.

Изменено в версии 3.11: В списке выражений теперь разрешено использовать элементы со звездочками.

8.4. Заявление try

Оператор try задает обработчики исключений и/или код очистки для группы операторов:

try_stmt  ::=  try1_stmt | try2_stmt | try3_stmt
try1_stmt ::=  "try" ":" suite
               ("except" [expression ["as" identifier]] ":" suite)+
               ["else" ":" suite]
               ["finally" ":" suite]
try2_stmt ::=  "try" ":" suite
               ("except" "*" expression ["as" identifier] ":" suite)+
               ["else" ":" suite]
               ["finally" ":" suite]
try3_stmt ::=  "try" ":" suite
               "finally" ":" suite

Дополнительную информацию об исключениях можно найти в разделе Исключения, а информацию об использовании оператора raise для генерации исключений - в разделе Заявление raise.

8.4.1. Оговорка except

В пункте (пунктах) except указывается один или несколько обработчиков исключений. Если в предложении try исключение не встречается, обработчик исключений не выполняется. Когда в наборе try возникает исключение, начинается поиск обработчика исключений. Этот поиск поочередно проверяет клаузы except, пока не будет найден тот, который соответствует исключению. Предложение except без выражения, если оно присутствует, должно быть последним; оно соответствует любому исключению. Для клаузы except с выражением это выражение оценивается, и клауза соответствует исключению, если полученный объект «совместим» с исключением. Объект совместим с исключением, если он является классом или non-virtual base class объекта исключения, или кортежем, содержащим элемент, который является классом или невиртуальным базовым классом объекта исключения.

Если ни одна из формул except не соответствует исключению, поиск обработчика исключения продолжается в окружающем коде и на стеке вызовов. [1]

Если оценка выражения в заголовке предложения except вызывает исключение, первоначальный поиск обработчика отменяется и начинается поиск нового исключения в окружающем коде и на стеке вызовов (это рассматривается так же, как если бы исключение вызвал весь оператор try).

Если найдено совпадающее предложение except, исключение присваивается цели, указанной после ключевого слова as в этом предложении except, если оно присутствует, и выполняется набор предложений except. Все except клаузулы должны иметь исполняемый блок. Когда достигается конец этого блока, выполнение продолжается после всего оператора try. (Это означает, что если для одного и того же исключения существуют два вложенных обработчика, и исключение возникает в пункте try внутреннего обработчика, внешний обработчик не будет обрабатывать исключение).

Если исключение было назначено с помощью as target, оно очищается в конце предложения except. Это происходит так, как если бы

except E as N:
    foo

был переведен на

except E as N:
    try:
        foo
    finally:
        del N

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

Перед выполнением набора except исключение сохраняется в модуле sys, откуда к нему можно получить доступ из тела except, вызвав sys.exception(). При выходе из обработчика исключений исключение, хранящееся в модуле sys, сбрасывается в прежнее значение:

>>> print(sys.exception())
None
>>> try:
...     raise TypeError
... except:
...     print(repr(sys.exception()))
...     try:
...          raise ValueError
...     except:
...         print(repr(sys.exception()))
...     print(repr(sys.exception()))
...
TypeError()
ValueError()
TypeError()
>>> print(sys.exception())
None

8.4.2. Оговорка except*

Клаузула(ы) except* используется для обработки ExceptionGroup. Тип исключения для сопоставления интерпретируется так же, как и в случае except, но в случае групп исключений мы можем иметь частичные совпадения, когда тип совпадает с некоторыми исключениями в группе. Это означает, что может выполняться несколько предложений except*, каждое из которых обрабатывает часть группы исключений. Каждое предложение выполняется не более одного раза и обрабатывает группу исключений, состоящую из всех совпадающих исключений. Каждое исключение в группе обрабатывается не более чем одним предложением except*, первым, которое ему соответствует.

>>> try:
...     raise ExceptionGroup("eg",
...         [ValueError(1), TypeError(2), OSError(3), OSError(4)])
... except* TypeError as e:
...     print(f'caught {type(e)} with nested {e.exceptions}')
... except* OSError as e:
...     print(f'caught {type(e)} with nested {e.exceptions}')
...
caught <class 'ExceptionGroup'> with nested (TypeError(2),)
caught <class 'ExceptionGroup'> with nested (OSError(3), OSError(4))
  + Exception Group Traceback (most recent call last):
  |   File "<stdin>", line 2, in <module>
  | ExceptionGroup: eg
  +-+---------------- 1 ----------------
    | ValueError: 1
    +------------------------------------

Все оставшиеся исключения, которые не были обработаны ни одним из положений except*, повторно поднимаются в конце, вместе со всеми исключениями, которые были подняты внутри положений except*. Если этот список содержит несколько исключений для повторного вызова, они объединяются в группу исключений.

Если поднятое исключение не является группой исключений и его тип соответствует одному из положений except*, оно перехватывается и оборачивается группой исключений с пустой строкой сообщения.

>>> try:
...     raise BlockingIOError
... except* BlockingIOError as e:
...     print(repr(e))
...
ExceptionGroup('', (BlockingIOError()))

Предложение except* должно иметь соответствующий тип, и этот тип не может быть подклассом BaseExceptionGroup. Невозможно смешивать except и except* в одном try. break, continue и return не могут встречаться в предложении except*.

8.4.3. Оговорка else

Необязательный пункт else выполняется, если поток управления покинул набор try, не было поднято исключение и не было выполнено ни одного оператора return, continue или break. Исключения в пункте else не обрабатываются предыдущими пунктами except.

8.4.4. Оговорка finally

Если присутствует finally, он определяет обработчик «очистки». Выполняется предложение try, включая все предложения except и else. Если в каком-либо из пунктов возникает исключение и оно не обрабатывается, исключение временно сохраняется. Выполняется предложение finally. Если исключение сохранилось, то оно вновь поднимается в конце предложения finally. Если в пункте finally будет поднято другое исключение, то сохраненное исключение будет установлено в качестве контекста нового исключения. Если в пункте finally выполняется оператор return, break или continue, сохраненное исключение отбрасывается:

>>> def f():
...     try:
...         1/0
...     finally:
...         return 42
...
>>> f()
42

Информация об исключениях недоступна программе во время выполнения клаузы finally.

Когда оператор return, break или continue выполняется в сюите try оператора tryfinally, клаузула finally также выполняется «на выходе».

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

>>> def foo():
...     try:
...         return 'try'
...     finally:
...         return 'finally'
...
>>> foo()
'finally'

Изменено в версии 3.8: До версии Python 3.8 оператор continue был недопустим в предложении finally из-за проблем с реализацией.

8.5. Заявление with

Оператор with используется для обертывания выполнения блока методами, определенными менеджером контекста (см. раздел С менеджерами контекста заявлений). Это позволяет инкапсулировать общие tryexceptfinally шаблоны использования для удобного повторного использования.

with_stmt          ::=  "with" ( "(" with_stmt_contents ","? ")" | with_stmt_contents ) ":" suite
with_stmt_contents ::=  with_item ("," with_item)*
with_item          ::=  expression ["as" target]

Выполнение оператора with с одним «элементом» происходит следующим образом:

  1. Выражение контекста (выражение, заданное в with_item) оценивается для получения менеджера контекста.

  2. Данные __enter__() контекстного менеджера загружаются для последующего использования.

  3. Данные __exit__() контекстного менеджера загружаются для последующего использования.

  4. Вызывается метод __enter__() менеджера контекста.

  5. Если цель была включена в оператор with, ей присваивается возвращаемое значение из __enter__().

    Примечание

    Оператор with гарантирует, что если метод __enter__() вернется без ошибки, то всегда будет вызван __exit__(). Таким образом, если ошибка произойдет во время присваивания целевому списку, она будет обработана так же, как и ошибка, произошедшая внутри набора. См. шаг 7 ниже.

  6. Набор выполнен.

  7. Вызывается метод __exit__() менеджера контекста. Если выход из набора вызван исключением, то в качестве аргументов в __exit__() передаются его тип, значение и обратная трассировка. В противном случае передаются три аргумента None.

    Если набор был завершен из-за исключения, а возвращаемое значение метода __exit__() было ложным, исключение повторяется. Если возвращаемое значение было истинным, исключение подавляется, и выполнение продолжается с оператора, следующего за оператором with.

    Если набор был завершен по любой причине, кроме исключения, возвращаемое значение из __exit__() игнорируется, и выполнение продолжается в обычном месте для того типа выхода, который был предпринят.

Следующий код:

with EXPRESSION as TARGET:
    SUITE

семантически эквивалентно:

manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
value = enter(manager)
hit_except = False

try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not exit(manager, *sys.exc_info()):
        raise
finally:
    if not hit_except:
        exit(manager, None, None, None)

При наличии более одного элемента контекстные менеджеры обрабатываются так, как если бы несколько операторов with были вложены друг в друга:

with A() as a, B() as b:
    SUITE

семантически эквивалентно:

with A() as a:
    with B() as b:
        SUITE

Вы также можете писать контекстные менеджеры с несколькими элементами в нескольких строках, если элементы окружены круглыми скобками. Например:

with (
    A() as a,
    B() as b,
):
    SUITE

Изменено в версии 3.1: Поддержка нескольких контекстных выражений.

Изменено в версии 3.10: Поддержка использования группирующих круглых скобок для разбиения утверждения на несколько строк.

См.также

PEP 343 - Утверждение «с»

Спецификация, история и примеры для оператора Python with.

8.6. Заявление match

Added in version 3.10.

Оператор match используется для сопоставления шаблонов. Синтаксис:

match_stmt   ::=  'match' subject_expr ":" NEWLINE INDENT case_block+ DEDENT
subject_expr ::=  star_named_expression "," star_named_expressions?
                  | named_expression
case_block   ::=  'case' patterns [guard] ":" block

Примечание

В этом разделе для обозначения soft keywords используются одинарные кавычки.

При сопоставлении шаблонов на вход подается шаблон (следующий case) и значение предмета (следующий match). Шаблон (который может содержать подшаблоны) сопоставляется со значением предмета. Результатами могут быть:

  • Успех или неудача совпадения (также называется успехом или неудачей шаблона).

  • Возможность привязки сопоставленных значений к имени. Необходимые для этого условия рассмотрены ниже.

Ключевыми словами match и case являются soft keywords.

См.также

  • PEP 634 – Структурное сопоставление шаблонов: спецификация

  • PEP 636 – Структурное сопоставление шаблонов: учебное пособие

8.6.1. Обзор

Вот обзор логического потока оператора соответствия:

  1. Выражение субъекта subject_expr оценивается, и в результате получается значение субъекта. Если выражение субъекта содержит запятую, то с помощью the standard rules строится кортеж.

  2. Каждый шаблон в case_block пытается совпасть со значением темы. Ниже описаны конкретные правила для успеха или неудачи. Попытка совпадения может также связать некоторые или все отдельные имена в шаблоне. Точные правила связывания имен зависят от типа шаблона и приведены ниже. Привязки имен, сделанные во время успешного совпадения шаблона, переживают выполненный блок и могут использоваться после оператора совпадения.

    Примечание

    При неудачном совпадении шаблонов некоторые подшаблоны могут быть успешными. Не полагайтесь на то, что при неудачном совпадении будут созданы привязки. И наоборот, не рассчитывайте на то, что переменные останутся неизменными после неудачного совпадения. Точное поведение зависит от реализации и может меняться. Это намеренное решение, принятое для того, чтобы позволить различным реализациям добавлять оптимизацию.

  3. Если шаблон работает успешно, оценивается соответствующая защита (если она есть). В этом случае гарантируется, что все связывания имен произошли.

    • Если защита оценивается как true или отсутствует, то выполняется block внутри case_block.

    • В противном случае выполняется следующая попытка case_block, как описано выше.

    • Если больше нет блоков case, оператор match завершается.

Примечание

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

Образец заявления о соответствии:

>>> flag = False
>>> match (100, 200):
...    case (100, 300):  # Mismatch: 200 != 300
...        print('Case 1')
...    case (100, 200) if flag:  # Successful match, but guard fails
...        print('Case 2')
...    case (100, y):  # Matches and binds y to 200
...        print(f'Case 3, y: {y}')
...    case _:  # Pattern not attempted
...        print('Case 4, I match anything!')
...
Case 3, y: 200

В данном случае if flag - это охранник. Подробнее об этом читайте в следующем разделе.

8.6.2. Охранники

guard ::=  "if" named_expression

Чтобы код внутри блока case выполнился, должен сработать guard (который является частью case). Он имеет вид: if, за которым следует выражение.

Логический поток блока case с блоком guard следующий:

  1. Проверьте, что шаблон в блоке case сработал. Если шаблон не сработал, блок guard не оценивается и проверяется следующий блок case.

  2. Если шаблон сработал, оцените значение guard.

    • Если условие guard оценивается как истинное, выбирается блок case.

    • Если условие guard оценивается как ложное, блок case не выбирается.

    • Если во время оценки guard возникает исключение, оно выводится на экран.

Стражам разрешено иметь побочные эффекты, поскольку они являются выражениями. Оценка стражей должна выполняться от первого до последнего блока случаев, по одному за раз, пропуская блоки случаев, чьи шаблоны не все успешны. (Т. е. оценка стражей должна происходить по порядку). Оценка стражей должна прекратиться, как только будет выбран блок case.

8.6.3. Неопровержимые аргументы

Неопровержимый блок прецедентов - это блок прецедентов, совпадающий со всеми. В утверждении match может быть только один неопровержимый блок case, и он должен быть последним.

Блок case считается неопровержимым, если у него нет защитника и его паттерн неопровержим. Паттерн считается неопровержимым, если мы можем доказать, исходя только из его синтаксиса, что он всегда будет успешным. Только следующие шаблоны являются неопровержимыми:

8.6.4. Узоры

Примечание

В этом разделе используются грамматические обозначения, выходящие за рамки стандартной EBNF:

  • обозначение SEP.RULE+ является сокращением для RULE (SEP RULE)*.

  • Обозначение !RULE - это сокращение для отрицательного утверждения с опережением.

Синтаксис верхнего уровня для patterns таков:

patterns       ::=  open_sequence_pattern | pattern
pattern        ::=  as_pattern | or_pattern
closed_pattern ::=  | literal_pattern
                    | capture_pattern
                    | wildcard_pattern
                    | value_pattern
                    | group_pattern
                    | sequence_pattern
                    | mapping_pattern
                    | class_pattern

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

8.6.4.1. ИЛИ Узоры

Шаблон OR - это два или более шаблонов, разделенных вертикальными полосами |. Синтаксис:

or_pattern ::=  "|".closed_pattern+

Только последний подшаблон может быть irrefutable, и каждый подшаблон должен связывать один и тот же набор имен, чтобы избежать двусмысленности.

Шаблон ИЛИ поочередно сопоставляет каждый из своих подшаблонов со значением предмета, пока не будет достигнут успех. Тогда шаблон ИЛИ считается успешным. В противном случае, если ни один из подшаблонов не достиг успеха, шаблон ИЛИ считается неудачным.

Проще говоря, P1 | P2 | ... будет пытаться соответствовать P1, если это не удастся, то будет пытаться соответствовать P2, сразу же добиваясь успеха, если удастся, и терпя неудачу в противном случае.

8.6.4.2. AS Patterns

Шаблон AS сопоставляет шаблон OR слева от ключевого слова as с темой. Синтаксис:

as_pattern ::=  or_pattern "as" capture_pattern

Если шаблон OR не работает, шаблон AS не работает. В противном случае шаблон AS связывает субъект с именем, стоящим справа от ключевого слова as, и добивается успеха. capture_pattern не может быть _.

Проще говоря, P as NAME будет совпадать с P, а в случае успеха установит NAME = <subject>.

8.6.4.3. Буквальные узоры

Буквальный шаблон соответствует большинству literals в Python. Синтаксис:

literal_pattern ::=  signed_number
                     | signed_number "+" NUMBER
                     | signed_number "-" NUMBER
                     | strings
                     | "None"
                     | "True"
                     | "False"
                     | signed_number: NUMBER | "-" NUMBER

Правило strings и маркер NUMBER определены в standard Python grammar. Поддерживаются строки с тройными кавычками. Поддерживаются сырые строки и байтовые строки. f-струны не поддерживаются.

Формы signed_number '+' NUMBER и signed_number '-' NUMBER служат для выражения complex numbers; для них требуется вещественное число слева и мнимое число справа. Например, 3 + 4j.

Проще говоря, LITERAL будет успешным только в том случае, если <subject> == LITERAL. Для синглетонов None, True и False используется оператор is.

8.6.4.4. Узоры для захвата

Шаблон захвата связывает значение предмета с именем. Синтаксис:

capture_pattern ::=  !'_' NAME

Одиночное подчеркивание _ не является шаблоном захвата (это то, что выражает !'_'). Вместо этого он рассматривается как wildcard_pattern.

В данном шаблоне данное имя может быть связано только один раз. Например, case x, x: ... недопустимо, а case [x] | x: ... разрешено.

Шаблоны захвата всегда успешны. Привязка следует правилам определения области видимости, установленным оператором выражения присваивания в PEP 572; имя становится локальной переменной в ближайшей области видимости содержащей функции, если нет применимого оператора global или nonlocal.

Проще говоря, NAME всегда будет успешным и установит NAME = <subject>.

8.6.4.5. Узоры с диким знаком

Шаблон подстановочного знака всегда успешен (соответствует всему) и не связывает ни одного имени. Синтаксис:

wildcard_pattern ::=  '_'

_ - это soft keyword в любом шаблоне, но только в шаблонах. Как обычно, он является идентификатором даже внутри match тематических выражений, guards и case блоков.

Проще говоря, _ всегда будет успешным.

8.6.4.6. Образцы ценностей

Шаблон значения представляет собой именованное значение в Python. Синтаксис:

value_pattern ::=  attr
attr          ::=  name_or_attr "." NAME
name_or_attr  ::=  attr | NAME

Имя с точкой в шаблоне ищется с помощью стандартного name resolution rules языка Python. Шаблон работает успешно, если найденное значение равно значению объекта (с помощью оператора равенства ==).

Проще говоря, NAME1.NAME2 будет успешным только в том случае, если <subject> == NAME1.NAME2

Примечание

Если одно и то же значение встречается несколько раз в одном и том же операторе соответствия, интерпретатор может кэшировать первое найденное значение и использовать его повторно, а не повторять один и тот же поиск. Этот кэш строго привязан к конкретному выполнению данного оператора соответствия.

8.6.4.7. Групповые узоры

Групповой шаблон позволяет пользователям добавлять круглые скобки вокруг шаблонов, чтобы подчеркнуть предполагаемую группировку. В остальном он не имеет дополнительного синтаксиса. Синтаксис:

group_pattern ::=  "(" pattern ")"

Проще говоря, (P) имеет тот же эффект, что и P.

8.6.4.8. Узоры последовательности

Шаблон последовательности содержит несколько подшаблонов для сопоставления с элементами последовательности. Синтаксис похож на распаковку списка или кортежа.

sequence_pattern       ::=  "[" [maybe_sequence_pattern] "]"
                            | "(" [open_sequence_pattern] ")"
open_sequence_pattern  ::=  maybe_star_pattern "," [maybe_sequence_pattern]
maybe_sequence_pattern ::=  ",".maybe_star_pattern+ ","?
maybe_star_pattern     ::=  star_pattern | pattern
star_pattern           ::=  "*" (capture_pattern | wildcard_pattern)

Нет разницы, используются ли круглые или квадратные скобки для шаблонов последовательности (например, (...) против [...]).

Примечание

Одиночный шаблон, заключенный в круглые скобки без запятой (например, (3 | 4)), является шаблоном group pattern. В то время как одиночный шаблон, заключенный в квадратные скобки (например, [3 | 4]), по-прежнему является шаблоном последовательности.

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

Ниже приведен логический процесс сопоставления шаблона последовательности со значением предмета:

  1. Если значение предмета не является последовательностью [2], шаблон последовательности не работает.

  2. Если значение субъекта является экземпляром str, bytes или bytearray, шаблон последовательности не работает.

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

    Если шаблон последовательности имеет фиксированную длину:

    1. Если длина предметной последовательности не равна количеству подпаттернов, шаблон последовательности не работает

    2. Подшаблоны в шаблоне последовательности сопоставляются с соответствующими элементами предметной последовательности слева направо. Сопоставление прекращается, как только какой-либо подшаблон терпит неудачу. Если все подшаблоны успешно сопоставлены с соответствующими элементами, шаблон последовательности успешен.

    Иначе, если шаблон последовательности имеет переменную длину:

    1. Если длина предметной последовательности меньше, чем количество незвездных подпаттернов, шаблон последовательности не работает.

    2. Ведущие незвездные подпаттерны сопоставляются с соответствующими элементами, как для последовательностей фиксированной длины.

    3. Если предыдущий шаг прошел успешно, то звездный подпаттерн соответствует списку, сформированному из оставшихся элементов предмета, исключая оставшиеся элементы, соответствующие незвездным подпаттернам, следующим за звездным подпаттерном.

    4. Оставшиеся незвездные подпаттерны сопоставляются с соответствующими предметами, как для последовательности фиксированной длины.

    Примечание

    Длина предметной последовательности получается через len() (т.е. через протокол __len__()). Эта длина может кэшироваться интерпретатором аналогично value patterns.

Проще говоря, [P1, P2, P3,, P<N>] совпадает только в том случае, если происходит все следующее:

  • Проверьте, что <subject> является последовательностью

  • len(subject) == <N>

  • P1 совпадает с <subject>[0] (обратите внимание, что это совпадение может также связывать имена)

  • P2 совпадает с <subject>[1] (обратите внимание, что это совпадение может также связывать имена)

  • … и так далее для соответствующей детали/элемента.

8.6.4.9. Картографирование узоров

Шаблон отображения содержит один или несколько шаблонов ключ-значение. Синтаксис аналогичен построению словаря. Синтаксис:

mapping_pattern     ::=  "{" [items_pattern] "}"
items_pattern       ::=  ",".key_value_pattern+ ","?
key_value_pattern   ::=  (literal_pattern | value_pattern) ":" pattern
                         | double_star_pattern
double_star_pattern ::=  "**" capture_pattern

В шаблоне отображения может быть не более одной детали с двойной звездой. Узор «двойная звезда» должен быть последним подшаблоном в шаблоне отображения.

Дублирование ключей в шаблонах отображения запрещено. Дублирование литеральных ключей приведет к ошибке SyntaxError. Два ключа, которые в противном случае имеют одинаковое значение, во время выполнения вызовут ошибку ValueError.

Ниже приведен логический процесс сопоставления шаблона сопоставления со значением субъекта:

  1. Если значение предмета не является отображением [3], шаблон отображения не работает.

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

  3. Если в шаблоне отображения обнаружены дублирующиеся ключи, шаблон считается недействительным. При дублировании литеральных значений выдается сообщение SyntaxError, а при дублировании именованных ключей с одинаковым значением - ValueError.

Примечание

Пары ключ-значение сопоставляются с помощью двухаргументной формы метода get() субъекта отображения. Сопоставляемые пары ключ-значение должны уже присутствовать в отображении, а не создаваться «на лету» с помощью __missing__() или __getitem__().

Проще говоря, {KEY1: P1, KEY2: P2, ... } совпадает только в том случае, если происходит все следующее:

  • Проверьте, что <subject> является отображением

  • KEY1 in <subject>

  • P1 совпадает с <subject>[KEY1]

  • … и так далее для соответствующей пары KEY/шаблон.

8.6.4.10. Узоры классов

Шаблон класса представляет класс и его позиционные и ключевые аргументы (если таковые имеются). Синтаксис:

class_pattern       ::=  name_or_attr "(" [pattern_arguments ","?] ")"
pattern_arguments   ::=  positional_patterns ["," keyword_patterns]
                         | keyword_patterns
positional_patterns ::=  ",".pattern+
keyword_patterns    ::=  ",".keyword_pattern+
keyword_pattern     ::=  NAME "=" pattern

Одно и то же ключевое слово не должно повторяться в шаблонах класса.

Ниже приведен логический процесс сопоставления шаблона класса со значением предмета:

  1. Если name_or_attr не является экземпляром встроенного type , поднимите TypeError.

  2. Если значение субъекта не является экземпляром name_or_attr (проверяется через isinstance()), шаблон класса не работает.

  3. Если аргументы шаблона отсутствуют, шаблон работает успешно. В противном случае последующие шаги зависят от того, присутствует ли в шаблоне ключевое слово или позиционный аргумент.

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

    Если присутствуют только шаблоны ключевых слов, они обрабатываются поочередно:

    I. Ключевое слово рассматривается как атрибут предмета.

    • Если при этом возникает исключение, отличное от AttributeError, то оно выводится на экран.

    • Если при этом возникает сообщение AttributeError, то шаблон класса не работает.

    • В противном случае подшаблон, связанный с шаблоном ключевого слова, сопоставляется со значением атрибута субъекта. Если это не удается, шаблон класса терпит неудачу; если это удается, выполняется переход к следующему ключевому слову.

    II. Если все шаблоны ключевых слов успешны, то и шаблон класса успешен.

    Если присутствуют позиционные шаблоны, они преобразуются в шаблоны ключевых слов с помощью атрибута __match_args__ на классе name_or_attr перед сопоставлением:

    I. Вызывается эквивалент getattr(cls, "__match_args__", ()).

    • Если при этом возникает исключение, то оно выводится на экран.

    • Если возвращаемое значение не является кортежем, преобразование завершится неудачей и будет поднят TypeError.

    • Если позиционных шаблонов больше, чем len(cls.__match_args__), то поднимается TypeError.

    • В противном случае позиционный шаблон i преобразуется в шаблон ключевого слова, используя __match_args__[i] в качестве ключевого слова. __match_args__[i] должен быть строкой; если это не так, то TypeError переводится в строку.

    • Если есть дублирующиеся ключевые слова, то будет поднята отметка TypeError.

    II. После преобразования всех позиционных шаблонов в шаблоны ключевых слов,

    совпадение происходит так же, как если бы в нем присутствовали только ключевые слова.

    Для следующих встроенных типов обработка позиционных подшаблонов отличается:

    Эти классы принимают один позиционный аргумент, и шаблон в них сопоставляется со всем объектом, а не с одним атрибутом. Например, int(0|1) соответствует значению 0, но не значению 0.0.

Проще говоря, CLS(P1, attr=P2) совпадает только в том случае, если происходит следующее:

  • isinstance(<subject>, CLS)

  • преобразуйте P1 в шаблон ключевого слова с помощью CLS.__match_args__.

  • Для каждого аргумента ключевого слова attr=P2:

    • hasattr(<subject>, "attr")

    • P2 совпадает с <subject>.attr

  • … и так далее для соответствующей пары ключевое слово-аргумент/шаблон.

См.также

  • PEP 634 – Структурное сопоставление шаблонов: спецификация

  • PEP 636 – Структурное сопоставление шаблонов: учебное пособие

8.7. Определения функций

Определение функции задает объект пользовательской функции (см. раздел Стандартная иерархия типов):

funcdef                   ::=  [decorators] "def" funcname [type_params] "(" [parameter_list] ")"
                               ["->" expression] ":" suite
decorators                ::=  decorator+
decorator                 ::=  "@" assignment_expression NEWLINE
parameter_list            ::=  defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]]
                                 | parameter_list_no_posonly
parameter_list_no_posonly ::=  defparameter ("," defparameter)* ["," [parameter_list_starargs]]
                               | parameter_list_starargs
parameter_list_starargs   ::=  "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
                               | "**" parameter [","]
parameter                 ::=  identifier [":" expression]
defparameter              ::=  parameter ["=" expression]
funcname                  ::=  identifier

Определение функции - это исполняемый оператор. Его выполнение связывает имя функции в текущем локальном пространстве имен с объектом функции (оберткой вокруг исполняемого кода функции). Этот объект функции содержит ссылку на текущее глобальное пространство имен в качестве глобального пространства имен, которое будет использоваться при вызове функции.

Определение функции не выполняет тело функции; оно выполняется только при вызове функции. [4]

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

@f1(arg)
@f2
def func(): pass

примерно эквивалентно

def func(): pass
func = f1(arg)(f2(func))

за исключением того, что исходная функция не привязана временно к имени func.

Изменено в версии 3.9: Функции могут быть украшены любым допустимым assignment_expression. Ранее грамматика была гораздо более строгой; подробности см. в PEP 614.

Список type parameters может быть указан в квадратных скобках между именем функции и открывающей круглой скобкой для списка ее параметров. Это указывает программам статической проверки типов, что функция является родовой. Во время выполнения параметры типа могут быть получены из атрибута __type_params__ функции. Подробнее см. в разделе Общие функции.

Изменено в версии 3.12: В Python 3.12 появились списки параметров типов.

Если один или несколько parameters имеют вид параметр = выражение, то говорят, что функция имеет «значения параметров по умолчанию». Для параметра, имеющего значение по умолчанию, соответствующий argument может быть опущен в вызове, и тогда вместо него подставляется значение параметра по умолчанию. Если параметр имеет значение по умолчанию, то все последующие параметры до «*» также должны иметь значение по умолчанию - это синтаксическое ограничение, которое не выражается грамматикой.

**Это означает, что выражение оценивается один раз, когда функция определена, и что при каждом вызове используется одно и то же «заранее вычисленное» значение. Это особенно важно понимать, когда значение параметра по умолчанию является изменяемым объектом, таким как список или словарь: если функция изменяет объект (например, добавляет элемент в список), значение параметра по умолчанию фактически изменяется. Как правило, это не то, что было задумано. Обходной путь - использовать None в качестве значения по умолчанию и явно проверять его в теле функции, например:

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin

Более подробно семантика вызова функции описана в разделе Звонки. Вызов функции всегда присваивает значения всем параметрам, указанным в списке параметров, либо из позиционных аргументов, либо из аргументов ключевых слов, либо из значений по умолчанию. Если присутствует форма «*identifier», то она инициализируется кортежем, принимающим все лишние позиционные параметры, по умолчанию - пустым кортежем. Если присутствует форма «**identifier», то она инициализируется новым упорядоченным отображением, принимающим любые избыточные аргументы ключевых слов, по умолчанию - новым пустым отображением того же типа. Параметры после «*» или «*identifier» являются параметрами только ключевого слова и могут передаваться только аргументами ключевого слова. Параметры перед «/» являются только позиционными параметрами и могут передаваться только позиционными аргументами.

Изменено в версии 3.8: Синтаксис параметров функции / может использоваться для обозначения параметров только позиционного типа. Подробности см. в разделе PEP 570.

Параметры могут иметь annotation вида «: expression» после имени параметра. Любой параметр может иметь аннотацию, даже те, которые имеют вид *identifier или **identifier. Функции могут иметь аннотацию «return» вида «-> expression» после списка параметров. Эти аннотации могут быть любым допустимым выражением Python. Наличие аннотаций не меняет семантику функции. Значения аннотаций доступны как значения словаря, ключ к которому находится в атрибуте __annotations__ объекта функции. Если используется импорт annotations из __future__, аннотации сохраняются в виде строк во время выполнения, что позволяет отложить их оценку. В противном случае они оцениваются при выполнении определения функции. В этом случае аннотации могут быть оценены не в том порядке, в котором они представлены в исходном коде.

Также можно создавать анонимные функции (функции, не привязанные к имени) для непосредственного использования в выражениях. Для этого используются лямбда-выражения, описанные в разделе Лямбды. Обратите внимание, что лямбда-выражение - это всего лишь сокращение для упрощенного определения функции; функцию, определенную в выражении «def», можно передавать по кругу или присваивать другому имени точно так же, как функцию, определенную лямбда-выражением. Форма «def» на самом деле более мощная, поскольку она позволяет выполнять несколько утверждений и аннотаций.

Примечание программиста: Функции - это объекты первого класса. Оператор «def», выполняемый внутри определения функции, определяет локальную функцию, которая может быть возвращена или передана. Свободные переменные, используемые во вложенной функции, могут обращаться к локальным переменным функции, содержащей def. Подробности см. в разделе Именование и связывание.

См.также

PEP 3107 - Аннотации функций

Оригинальная спецификация для аннотаций функций.

PEP 484 - Тип Подсказки

Определение стандартного значения для аннотаций: подсказки типов.

PEP 526 - Синтаксис для аннотаций переменных

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

PEP 563 - Отложенная оценка аннотаций

Поддержка прямых ссылок внутри аннотаций за счет сохранения аннотаций в строковой форме во время выполнения вместо нетерпеливой оценки.

PEP 318 - Декораторы для функций и методов

Были введены декораторы функций и методов. Декораторы классов были введены в PEP 3129.

8.8. Определения классов

Определение класса определяет объект класса (см. раздел Стандартная иерархия типов):

classdef    ::=  [decorators] "class" classname [type_params] [inheritance] ":" suite
inheritance ::=  "(" [argument_list] ")"
classname   ::=  identifier

Определение класса - это исполняемый оператор. Список наследования обычно представляет собой список базовых классов (см. Метаклассы для более продвинутого использования), поэтому каждый элемент списка должен оцениваться как объект класса, который допускает подклассификацию. Классы без списка наследования по умолчанию наследуют от базового класса object; следовательно,

class Foo:
    pass

эквивалентно

class Foo(object):
    pass

Затем набор класса выполняется в новом кадре выполнения (см. Именование и связывание), используя вновь созданное локальное пространство имен и исходное глобальное пространство имен. (Обычно набор содержит в основном определения функций). Когда набор класса завершает выполнение, его кадр выполнения отбрасывается, но локальное пространство имен сохраняется. [5] Затем создается объект класса с использованием списка наследования базовых классов и сохраненного локального пространства имен для словаря атрибутов. Имя класса привязывается к этому объекту класса в исходном локальном пространстве имен.

Порядок, в котором атрибуты были определены в теле класса, сохраняется в __dict__ нового класса. Обратите внимание, что это можно сделать только сразу после создания класса и только для классов, которые были определены с помощью синтаксиса определения.

Создание классов может быть сильно изменено с помощью metaclasses.

Классы также могут быть украшены: как и при украшении функций,

@f1(arg)
@f2
class Foo: pass

примерно эквивалентно

class Foo: pass
Foo = f1(arg)(f2(Foo))

Правила оценки для выражений декораторов такие же, как и для декораторов функций. Результат привязывается к имени класса.

Изменено в версии 3.9: Классы могут быть украшены любым допустимым assignment_expression. Ранее грамматика была гораздо более строгой; подробности см. в PEP 614.

Сразу после имени класса в квадратных скобках может быть указан список type parameters. Это указывает программам статической проверки типов, что класс является общим. Во время выполнения параметры типа могут быть получены из атрибута __type_params__ класса. Подробнее см. в разделе Общие классы.

Изменено в версии 3.12: В Python 3.12 появились списки параметров типов.

Примечание программиста: Переменные, заданные в определении класса, являются атрибутами класса; они разделяются экземплярами. Атрибуты экземпляра могут быть заданы в методе с помощью self.name = value. Атрибуты класса и экземпляра доступны через обозначение «self.name», и атрибут экземпляра скрывает одноименный атрибут класса при таком обращении. Атрибуты класса можно использовать в качестве значений по умолчанию для атрибутов экземпляра, но использование в них изменяемых значений может привести к неожиданным результатам. Descriptors можно использовать для создания переменных экземпляра с различными деталями реализации.

См.также

PEP 3115 - Метаклассы в Python 3000

Предложение, изменившее объявление метаклассов до текущего синтаксиса, и семантику построения классов с метаклассами.

PEP 3129 - Декораторы классов

Предложение, добавившее декораторы классов. Декораторы функций и методов были введены в PEP 318.

8.9. Корутины

Added in version 3.5.

8.9.1. Определение функции корутины

async_funcdef ::=  [decorators] "async" "def" funcname "(" [parameter_list] ")"
                   ["->" expression] ":" suite

Выполнение корутинов Python может быть приостановлено и возобновлено во многих точках (см. coroutine). Выражения await, async for и async with можно использовать только в теле функции-коробочки.

Функции, заданные с помощью синтаксиса async def, всегда являются coroutine-функциями, даже если они не содержат ключевых слов await или async.

Использовать выражение SyntaxError в теле функции coroutine - это yield from.

Пример корутинной функции:

async def func(param1, param2):
    do_stuff()
    await some_coroutine()

Изменено в версии 3.7: await и async теперь являются ключевыми словами; ранее они рассматривались как таковые только в теле функции coroutine.

8.9.2. Заявление async for

async_for_stmt ::=  "async" for_stmt

Метод asynchronous iterable предоставляет метод __aiter__, который непосредственно возвращает метод asynchronous iterator, который может вызывать асинхронный код в своем методе __anext__.

Оператор async for обеспечивает удобную итерацию над асинхронными итерациями.

Следующий код:

async for TARGET in ITER:
    SUITE
else:
    SUITE2

Семантически эквивалентно:

iter = (ITER)
iter = type(iter).__aiter__(iter)
running = True

while running:
    try:
        TARGET = await type(iter).__anext__(iter)
    except StopAsyncIteration:
        running = False
    else:
        SUITE
else:
    SUITE2

Подробнее см. также __aiter__() и __anext__().

Использовать оператор async for вне тела функции coroutine - это SyntaxError.

8.9.3. Заявление async with

async_with_stmt ::=  "async" with_stmt

asynchronous context manager - это context manager, который может приостановить выполнение в своих методах вход и выход.

Следующий код:

async with EXPRESSION as TARGET:
    SUITE

семантически эквивалентно:

manager = (EXPRESSION)
aenter = type(manager).__aenter__
aexit = type(manager).__aexit__
value = await aenter(manager)
hit_except = False

try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not await aexit(manager, *sys.exc_info()):
        raise
finally:
    if not hit_except:
        await aexit(manager, None, None, None)

Подробнее см. также __aenter__() и __aexit__().

Использовать оператор async with вне тела функции coroutine - это SyntaxError.

См.также

PEP 492 - Корутины с синтаксисом async и await

Предложение, которое сделало coroutines отдельной концепцией в Python и добавило поддерживающий синтаксис.

8.10. Списки параметров типа

Added in version 3.12.

Изменено в версии 3.13: Добавлена поддержка значений по умолчанию (см. PEP 696).

type_params  ::=  "[" type_param ("," type_param)* "]"
type_param   ::=  typevar | typevartuple | paramspec
typevar      ::=  identifier (":" expression)? ("=" expression)?
typevartuple ::=  "*" identifier ("=" expression)?
paramspec    ::=  "**" identifier ("=" expression)?

Functions (включая coroutines), classes и type aliases могут содержать список параметров типа:

def max[T](args: list[T]) -> T:
    ...

async def amax[T](args: list[T]) -> T:
    ...

class Bag[T]:
    def __iter__(self) -> Iterator[T]:
        ...

    def add(self, arg: T) -> None:
        ...

type ListOrSet[T] = list[T] | set[T]

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

Параметры типа объявляются в квадратных скобках ([]) сразу после имени функции, класса или псевдонима типа. Параметры типа доступны в пределах области видимости родового объекта, но не в других местах. Так, после объявления def func[T](): pass имя T недоступно в области видимости модуля. Ниже семантика родовых объектов описывается более точно. Область видимости параметров типа моделируется с помощью специальной функции (технически, annotation scope), которая оборачивает создание родового объекта.

Общие функции, классы и псевдонимы типов имеют атрибут __type_params__, перечисляющий их параметры типа.

Параметры типа бывают трех видов:

  • typing.TypeVar, представленный обычным именем (например, T). Семантически это представляет один тип для программы проверки типов.

  • typing.TypeVarTuple, представленный именем, снабженным одной звездочкой (например, *Ts). Семантически это означает кортеж из любого количества типов.

  • typing.ParamSpec, вводимое именем с префиксом из двух звездочек (например, **P). Семантически это обозначает параметры вызываемого объекта.

В объявлениях typing.TypeVar можно определить границы и ограничения с помощью двоеточия (:), за которым следует выражение. Одиночное выражение после двоеточия означает ограничение (например, T: int). Семантически это означает, что typing.TypeVar может представлять только типы, являющиеся подтипом этого ограничения. Кортеж выражений в круглых скобках после двоеточия указывает на набор ограничений (например, T: (str, bytes)). Каждый член кортежа должен быть типом (опять же, это не обязательно во время выполнения). Переменные типа с ограничениями могут принимать только один из типов в списке ограничений.

Для typing.TypeVar, объявленных с использованием синтаксиса списка параметров типа, границы и ограничения оцениваются не при создании родового объекта, а только при явном обращении к значению через атрибуты __bound__ и __constraints__. Для этого границы или ограничения оцениваются в отдельном annotation scope.

typing.TypeVarTuples и typing.ParamSpecs не могут иметь границ или ограничений.

Все три вида параметров типа могут также иметь значение по умолчанию, которое используется, когда параметр типа не указан явно. Оно добавляется путем добавления одного знака равенства (=), за которым следует выражение. Как и границы и ограничения переменных типа, значение по умолчанию оценивается не при создании объекта, а только при обращении к атрибуту __default__ параметра типа. Для этого значение по умолчанию оценивается в отдельном annotation scope. Если значение по умолчанию для параметра типа не указано, атрибут __default__ устанавливается в специальный объект-дозорный typing.NoDefault.

Следующий пример показывает полный набор разрешенных объявлений параметров типа:

def overly_generic[
   SimpleTypeVar,
   TypeVarWithDefault = int,
   TypeVarWithBound: int,
   TypeVarWithConstraints: (str, bytes),
   *SimpleTypeVarTuple = (int, float),
   **SimpleParamSpec = (str, bytearray),
](
   a: SimpleTypeVar,
   b: TypeVarWithDefault,
   c: TypeVarWithBound,
   d: Callable[SimpleParamSpec, TypeVarWithConstraints],
   *e: SimpleTypeVarTuple,
): ...

8.10.1. Общие функции

Родовые функции объявляются следующим образом:

def func[T](arg: T): ...

Этот синтаксис эквивалентен:

annotation-def TYPE_PARAMS_OF_func():
    T = typing.TypeVar("T")
    def func(arg: T): ...
    func.__type_params__ = (T,)
    return func
func = TYPE_PARAMS_OF_func()

Здесь annotation-def указывает на annotation scope, который на самом деле не привязан ни к какому имени во время выполнения. (В переводе допущена еще одна вольность: синтаксис не использует доступ к атрибутам модуля typing, а создает экземпляр typing.TypeVar напрямую).

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

Следующий пример иллюстрирует правила определения объема для этих случаев, а также для дополнительных вариантов параметров типа:

@decorator
def func[T: int, *Ts, **P](*args: *Ts, arg: Callable[P, T] = some_default):
    ...

За исключением lazy evaluation границы TypeVar, это эквивалентно:

DEFAULT_OF_arg = some_default

annotation-def TYPE_PARAMS_OF_func():

    annotation-def BOUND_OF_T():
        return int
    # In reality, BOUND_OF_T() is evaluated only on demand.
    T = typing.TypeVar("T", bound=BOUND_OF_T())

    Ts = typing.TypeVarTuple("Ts")
    P = typing.ParamSpec("P")

    def func(*args: *Ts, arg: Callable[P, T] = DEFAULT_OF_arg):
        ...

    func.__type_params__ = (T, Ts, P)
    return func
func = decorator(TYPE_PARAMS_OF_func())

Имена с заглавной буквы, такие как DEFAULT_OF_arg, на самом деле не связаны во время выполнения.

8.10.2. Общие классы

Родовые классы объявляются следующим образом:

class Bag[T]: ...

Этот синтаксис эквивалентен:

annotation-def TYPE_PARAMS_OF_Bag():
    T = typing.TypeVar("T")
    class Bag(typing.Generic[T]):
        __type_params__ = (T,)
        ...
    return Bag
Bag = TYPE_PARAMS_OF_Bag()

Здесь снова annotation-def (не настоящее ключевое слово) указывает на annotation scope, а имя TYPE_PARAMS_OF_Bag на самом деле не связано во время выполнения.

Родовые классы неявно наследуют от typing.Generic. Базовые классы и аргументы ключевых слов общих классов оцениваются в пределах области видимости для параметров типа, а декораторы оцениваются вне этой области. Это иллюстрируется следующим примером:

@decorator
class Bag(Base[T], arg=T): ...

Это эквивалентно:

annotation-def TYPE_PARAMS_OF_Bag():
    T = typing.TypeVar("T")
    class Bag(Base[T], typing.Generic[T], arg=T):
        __type_params__ = (T,)
        ...
    return Bag
Bag = decorator(TYPE_PARAMS_OF_Bag())

8.10.3. Псевдонимы общих типов

Оператор type также может использоваться для создания псевдонима общего типа:

type ListOrSet[T] = list[T] | set[T]

За исключением lazy evaluation значения, это эквивалентно:

annotation-def TYPE_PARAMS_OF_ListOrSet():
    T = typing.TypeVar("T")

    annotation-def VALUE_OF_ListOrSet():
        return list[T] | set[T]
    # In reality, the value is lazily evaluated
    return typing.TypeAliasType("ListOrSet", VALUE_OF_ListOrSet(), type_params=(T,))
ListOrSet = TYPE_PARAMS_OF_ListOrSet()

Здесь annotation-def (не настоящее ключевое слово) обозначает annotation scope. Имена с заглавной буквы, такие как TYPE_PARAMS_OF_ListOrSet, на самом деле не связаны во время выполнения.

Сноски