4. Модель выполнения

4.1. Структура программы

Программа на Python строится из блоков кода. Блок block - это часть текста программы на Python, которая выполняется как единое целое. Блоками являются: модуль, тело функции и определение класса. Каждая команда, набранная в интерактивном режиме, является блоком. Файл сценария (файл, переданный интерпретатору в качестве стандартного ввода или указанный интерпретатору в качестве аргумента командной строки) является блоком кода. Команда скрипта (команда, указанная в командной строке интерпретатора с опцией -c) - это блок кода. Модуль, запускаемый как скрипт верхнего уровня (как модуль __main__) из командной строки с помощью аргумента -m, также является блоком кода. Строковый аргумент, передаваемый встроенным функциям eval() и exec(), является блоком кода.

Блок кода выполняется в execution frame. Фрейм содержит некоторую административную информацию (используется для отладки) и определяет, где и как продолжается выполнение после завершения выполнения блока кода.

4.2. Именование и связывание

4.2.1. Привязка имен

Names относятся к объектам. Имена вводятся с помощью операций связывания имен.

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

  • формальные параметры функций,

  • определения классов,

  • определения функций,

  • выражения присвоения,

  • targets, которые являются идентификаторами, если встречаются в присваивании:

    • for Заголовок цикла,

    • после as в выражении with, предложении except, предложении except* или в as-pattern при структурном сопоставлении шаблонов,

    • в образце захвата при структурном сопоставлении образцов

  • import заявления.

  • type заявления.

  • type parameter lists.

Оператор import в форме from ... import * связывает все имена, определенные в импортируемом модуле, кроме тех, которые начинаются с подчеркивания. Эта форма может использоваться только на уровне модуля.

Цель, встречающаяся в операторе del, также считается связанной для этой цели (хотя на самом деле семантика заключается в отвязывании имени).

Каждый оператор присваивания или импорта выполняется в блоке, определенном определением класса или функции, или на уровне модуля (блок кода верхнего уровня).

Если имя связано в блоке, оно является локальной переменной этого блока, если только не объявлено как nonlocal или global. Если имя связано на уровне модуля, оно является глобальной переменной. (Переменные блока кода модуля являются локальными и глобальными). Если переменная используется в блоке кода, но не определена в нем, она является free variable.

Каждое появление имени в тексте программы относится к binding этого имени, установленному следующими правилами разрешения имен.

4.2.2. Разрешение имен

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

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

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

Если операция связывания имени происходит в любом месте блока кода, все использования имени в блоке рассматриваются как ссылки на текущий блок. Это может привести к ошибкам, когда имя используется внутри блока до его связывания. Это правило очень тонкое. В Python нет деклараций, и операции связывания имен могут происходить в любом месте блока кода. Локальные переменные блока кода можно определить, просканировав весь текст блока на предмет операций связывания имен. Примеры см. в разделе the FAQ entry on UnboundLocalError.

Если оператор global встречается внутри блока, то все использования имен, указанных в этом операторе, относятся к привязкам этих имен в пространстве имен верхнего уровня. Имена разрешаются в пространстве имен верхнего уровня путем поиска в глобальном пространстве имен, то есть в пространстве имен модуля, содержащего блок кода, и в пространстве имен builtins, пространстве имен модуля builtins. Сначала выполняется поиск в глобальном пространстве имен. Если имена в нем не найдены, то следующим ищется пространство имен builtins. Если имена не найдены и в пространстве имен builtins, то новые переменные создаются в глобальном пространстве имен. Утверждение global должно предшествовать всем случаям использования перечисленных имен.

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

Оператор nonlocal заставляет соответствующие имена ссылаться на ранее связанные переменные в ближайшей объемлющей области видимости функции. Утверждение SyntaxError возникает во время компиляции, если данное имя не существует ни в одной из объемлющих функций. Оператор Type parameters не может быть восстановлен с помощью оператора nonlocal.

Пространство имен для модуля создается автоматически при первом импорте модуля. Главный модуль скрипта всегда называется __main__.

Блоки определения классов и аргументы к exec() и eval() являются специальными в контексте разрешения имен. Определение класса - это исполняемый оператор, который может использовать и определять имена. Эти ссылки следуют обычным правилам разрешения имен, за исключением того, что несвязанные локальные переменные ищутся в глобальном пространстве имен. Пространство имен определения класса становится словарем атрибутов класса. Область видимости имен, определенных в блоке класса, ограничена блоком класса; она не распространяется на блоки кода методов. Сюда входят осмысления и генераторы выражений, но не входят annotation scopes, которые имеют доступ к своим объемлющим областям классов. Это означает, что следующие действия будут неудачными:

class A:
    a = 42
    b = list(a + i for i in range(10))

Тем не менее, удастся сделать следующее:

class A:
    type Alias = Nested
    class Nested: pass

print(A.Alias.__value__)  # <type 'A.Nested'>

4.2.3. Сферы применения аннотаций

Операторы Type parameter lists и type вводят диапазоны аннотаций, которые ведут себя в основном как диапазоны функций, но с некоторыми исключениями, о которых речь пойдет ниже. Операторы Annotations в настоящее время не используют диапазоны аннотаций, но ожидается, что они будут использовать диапазоны аннотаций в Python 3.13, когда будет реализован PEP 649.

Области аннотаций используются в следующих контекстах:

  • Списки параметров типа для generic type aliases.

  • Списки параметров типа generic functions. Аннотации общей функции выполняются в области действия аннотации, но ее параметры по умолчанию и декораторы - нет.

  • Списки параметров типа для generic classes. Базовые классы и аргументы ключевых слов общего класса выполняются в области действия аннотации, а его декораторы - нет.

  • Границы, ограничения и значения по умолчанию для параметров типа (lazily evaluated).

  • Значение псевдонимов типов (lazily evaluated).

Области аннотаций отличаются от областей функций следующим образом:

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

  • Выражения в диапазонах аннотаций не могут содержать выражения yield, yield from, await или :=. (Эти выражения разрешены в других диапазонах, содержащихся в аннотационном диапазоне).

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

  • Хотя аннотационные области имеют внутреннее имя, это имя не отражается в __qualname__ объектов, определенных в этой области. Вместо этого __qualname__ таких объектов будет таким, как если бы объект был определен в объемлющей области видимости.

Added in version 3.12: Области аннотаций были введены в Python 3.12 как часть PEP 695.

Изменено в версии 3.13: Области аннотаций также используются для параметров типа по умолчанию, как это было введено в PEP 696.

4.2.4. Ленивая оценка

Значения псевдонимов типов, созданных с помощью оператора type, оцениваются однозначно. То же самое относится к границам, ограничениям и значениям по умолчанию переменных типов, созданных с помощью оператора type parameter syntax. Это означает, что они не оцениваются при создании псевдонима типа или переменной типа. Вместо этого они оцениваются только тогда, когда это необходимо для разрешения доступа к атрибуту.

Пример:

>>> type Alias = 1/0
>>> Alias.__value__
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero
>>> def func[T: 1/0](): pass
>>> T = func.__type_params__[0]
>>> T.__bound__
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero

Здесь исключение возникает только при обращении к атрибуту __value__ псевдонима типа или атрибуту __bound__ переменной типа.

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

from typing import Literal

type SimpleExpr = int | Parenthesized
type Parenthesized = tuple[Literal["("], Expr, Literal[")"]]
type Expr = SimpleExpr | tuple[SimpleExpr, Literal["+", "-"], Expr]

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

Added in version 3.12.

4.2.5. Встроенные модули и ограниченное выполнение

Детали реализации CPython: Пользователям не следует трогать __builtins__; это исключительно деталь реализации. Пользователи, желающие переопределить значения в пространстве имен builtins, должны import модуль builtins и соответствующим образом изменить его атрибуты.

Пространство имен builtins, связанное с выполнением блока кода, фактически находится путем поиска имени __builtins__ в его глобальном пространстве имен; это должен быть словарь или модуль (в последнем случае используется словарь модуля). По умолчанию, если модуль __main__, то __builtins__ - это встроенный модуль builtins; если модуль любой другой, то __builtins__ - это псевдоним для словаря самого модуля builtins.

4.2.6. Взаимодействие с динамическими функциями

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

i = 10
def f():
    print(i)
i = 42
f()

Функции eval() и exec() не имеют доступа к полному окружению для разрешения имен. Имена могут быть разрешены в локальном и глобальном пространствах имен вызывающей стороны. Свободные переменные разрешаются не в ближайшем окружающем пространстве имен, а в глобальном пространстве имен. [1] Функции exec() и eval() имеют необязательные аргументы для переопределения глобального и локального пространства имен. Если указано только одно пространство имен, оно используется для обоих.

4.3. Исключения

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

Интерпретатор Python поднимает исключение, когда обнаруживает ошибку времени выполнения (например, деление на ноль). Программа Python также может явно вызвать исключение с помощью оператора raise. Обработчики исключений задаются с помощью операторов tryexcept. Пункт finally такого оператора можно использовать для указания очищающего кода, который не обрабатывает исключение, но выполняется независимо от того, произошло ли исключение в предыдущем коде или нет.

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

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

Исключения идентифицируются экземплярами классов. Клаузула except выбирается в зависимости от класса экземпляра: она должна ссылаться на класс экземпляра или его non-virtual base class. Экземпляр может быть получен обработчиком и может содержать дополнительную информацию об исключительном состоянии.

Примечание

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

См. также описание оператора try в разделе Заявление try и оператора raise в разделе Заявление raise.

Сноски