Модель выполнения
4.1. Структура программы
Программа на Python состоит из блоков кода. Блок — это фрагмент текста программы на Python, который выполняется как единое целое. К блокам относятся модуль, тело функции и определение класса. Каждая команда, введённая в интерактивном режиме, является блоком. Файл скрипта (файл, переданный интерпретатору в качестве стандартного ввода или указанный как аргумент командной строки интерпретатора) является блоком кода. Команда скрипта (команда, указанная в командной строке интерпретатора с параметром -c) является блоком кода. Модуль, запущенный из командной строки как скрипт верхнего уровня (в качестве модуля __main__) с аргументом -m, также является блоком кода. Строковый аргумент, передаваемый встроенным функциям eval() и exec(), является блоком кода.
Блок кода выполняется в кадре выполнения. Кадр содержит некоторую служебную информацию (используемую для отладки) и определяет, где и как продолжится выполнение после завершения блока кода.
4.2. Именование и связывание
4.2.1. Связывание имён
Имена ссылаются на объекты. Имена вводятся операциями связывания имён.
Следующие конструкции связывают имена:
- формальные параметры функций,
- определения классов,
- определения функций,
- выражения присваивания,
- цели, являющиеся идентификаторами, если они встречаются в присваивании:
-
заголовок цикла
for, - после
asв оператореwith, в предложенииexceptилиexcept*, либо в шаблоне as при структурном сопоставлении с образцом, - в шаблоне захвата при структурном сопоставлении с образцом
-
операторы
import. -
операторы
type. - списки параметров типа.
Оператор import вида from ... import * связывает все имена, определённые в импортированном модуле, кроме тех, которые начинаются с подчёркивания. Эта форма может использоваться только на уровне модуля.
Цель, встречающаяся в операторе del, также считается связанной для этой цели (хотя фактическая семантика состоит в отвязывании имени).
Каждый оператор присваивания или импорта находится внутри блока, определённого классом или функцией, либо на уровне модуля (в блоке кода верхнего уровня).
Если имя связано в блоке, оно является локальной переменной этого блока, если только оно не объявлено как nonlocal или global. Если имя связано на уровне модуля, оно является глобальной переменной. (Переменные блока кода модуля являются локальными и глобальными.) Если переменная используется в блоке кода, но не определена в нём, она является свободной переменной.
Каждое вхождение имени в тексте программы ссылается на связывание этого имени, установленное следующими правилами разрешения имён.
4.2.2. Разрешение имён
Область видимости определяет видимость имени внутри блока. Если в блоке определена локальная переменная, её область видимости включает этот блок. Если определение находится в блоке функции, область видимости распространяется на любые блоки, содержащиеся в блоке, где это имя определено, если только вложенный блок не вводит для этого имени другое связывание.
Когда имя используется в блоке кода, оно разрешается с использованием ближайшей внешней области видимости. Совокупность всех таких областей видимости, доступных блоку кода, называется окружением блока.
Если имя не найдено, возникает исключение NameError. Если текущая область видимости является областью видимости функции, а имя ссылается на локальную переменную, которой ещё не присвоено значение в точке использования имени, возникает исключение UnboundLocalError. UnboundLocalError является подклассом NameError.
Если операция связывания имени встречается где-либо в блоке кода, все использования этого имени внутри блока считаются ссылками на текущий блок. Это может приводить к ошибкам, когда имя используется в блоке до того, как оно связано. Это правило неочевидно. В Python нет объявлений, и операции связывания имён могут встречаться где угодно в блоке кода. Локальные переменные блока кода можно определить, просмотрев весь текст блока в поисках операций связывания имён. Примеры см. в ответе на часто задаваемый вопрос об UnboundLocalError.
Если в блоке встречается оператор global, все использования указанных в нём имён ссылаются на связывания этих имён в пространстве имён верхнего уровня. Имена разрешаются в пространстве имён верхнего уровня путём поиска в глобальном пространстве имён, то есть в пространстве имён модуля, содержащего блок кода, и в пространстве имён встроенных объектов — пространстве имён модуля builtins. Сначала выполняется поиск в глобальном пространстве имён. Если имена там не найдены, затем выполняется поиск в пространстве имён встроенных объектов. Если имена не найдены и там, в глобальном пространстве имён создаются новые переменные. Оператор global должен предшествовать всем использованиям перечисленных имён.
Оператор global имеет ту же область видимости, что и операция связывания имени в том же блоке. Если ближайшая внешняя область видимости для свободной переменной содержит оператор global, свободная переменная считается глобальной.
Оператор nonlocal указывает, что соответствующие имена ссылаются на ранее связанные переменные в ближайшей внешней области видимости функции. При компиляции возникает SyntaxError, если указанное имя не существует ни в одной внешней области видимости функции. Параметры типа нельзя повторно связывать с помощью оператора nonlocal.
Пространство имён модуля автоматически создаётся при первом импорте модуля. Главный модуль скрипта всегда называется __main__.
Блоки определений классов и аргументы функций exec() и eval() являются особым случаем при разрешении имён. Определение класса — это исполняемый оператор, который может использовать и определять имена. Для этих ссылок действуют обычные правила разрешения имён, за исключением того, что несвязанные локальные переменные ищутся в глобальном пространстве имён. Пространство имён определения класса становится словарём атрибутов класса. Область видимости имён, определённых в блоке класса, ограничена этим блоком; она не распространяется на блоки кода методов. Это относится к включениям и генераторным выражениям, но не к областям видимости аннотаций, которым доступны внешние области видимости класса. Это означает, что следующий код завершится ошибкой:
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 вводят области видимости аннотаций, которые в основном ведут себя как области видимости функций, но имеют некоторые описанные ниже исключения.
Области видимости аннотаций используются в следующих контекстах:
- Аннотации функций.
- Аннотации переменных.
- Списки параметров типа для обобщённых псевдонимов типов.
- Списки параметров типа для обобщённых функций. Аннотации обобщённой функции выполняются в области видимости аннотаций, а значения по умолчанию и декораторы — нет.
- Списки параметров типа для обобщённых классов. Базовые классы и именованные аргументы обобщённого класса выполняются в области видимости аннотаций, а декораторы — нет.
- Границы, ограничения и значения по умолчанию параметров типа (вычисляются отложенно).
- Значения псевдонимов типов (вычисляются отложенно).
Области видимости аннотаций отличаются от областей видимости функций следующим:
- Областям видимости аннотаций доступны пространства имён внешних классов. Если область видимости аннотаций непосредственно вложена в область видимости класса или в другую область видимости аннотаций, непосредственно вложенную в область видимости класса, код в области видимости аннотаций может использовать имена, определённые в области видимости класса, как если бы он выполнялся непосредственно в теле класса. Это отличается от обычных функций, определённых внутри классов: им недоступны имена, определённые в области видимости класса.
- Выражения в областях видимости аннотаций не могут содержать выражения
yield,yield from,awaitили:=. (Эти выражения разрешены в других областях видимости, вложенных в область видимости аннотаций.) - Имена, определённые в областях видимости аннотаций, нельзя повторно связывать операторами
nonlocalво вложенных областях видимости. Это касается только параметров типа, поскольку никакие другие синтаксические элементы, которые могут встречаться в областях видимости аннотаций, не вводят новые имена. - Хотя у областей видимости аннотаций есть внутреннее имя, оно не отражается в квалифицированном имени объектов, определённых в этой области. Вместо этого атрибут
__qualname__таких объектов выглядит так, как если бы объект был определён во внешней области видимости.
Добавлено в версии 3.12: Области видимости аннотаций появились в Python 3.12 в рамках PEP 695.
Изменено в версии 3.13: Области видимости аннотаций также используются для значений параметров типа по умолчанию, как предусмотрено в PEP 696.
4.2.4. Отложенное вычисление
Большинство областей видимости аннотаций вычисляются отложенно. Сюда входят аннотации, значения псевдонимов типов, созданных оператором type, а также границы, ограничения и значения по умолчанию переменных типа, созданных с помощью синтаксиса параметров типа. Это означает, что они вычисляются не при создании псевдонима типа или переменной типа и не при создании объекта с аннотациями. Вместо этого они вычисляются только при необходимости, например при обращении к атрибуту __value__ псевдонима типа.
Пример:
>>> 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]
Отложенно вычисляемые значения вычисляются в области видимости аннотаций, а это означает, что имена внутри такого значения ищутся так, как если бы они использовались в непосредственно внешней области видимости.
Добавлено в версии 3.12.
4.2.5. Встроенные объекты и ограниченное выполнение
Особенность реализации CPython: Пользователям не следует изменять __builtins__; это исключительно внутренняя особенность реализации. Пользователям, желающим переопределить значения в пространстве имён встроенных объектов, следует import модуль 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. Обработчики исключений задаются оператором try … except. Предложение finally такого оператора можно использовать для указания кода очистки, который не обрабатывает исключение, но выполняется независимо от того, произошло ли исключение в предшествующем коде.
В Python используется модель обработки ошибок с «завершением»: обработчик исключения может выяснить, что произошло, и продолжить выполнение на внешнем уровне, но не может устранить причину ошибки и повторить неудачную операцию (кроме как снова выполнив проблемный фрагмент кода с самого начала).
Если исключение вообще не обрабатывается, интерпретатор завершает выполнение программы или возвращается к главному интерактивному циклу. В обоих случаях он выводит трассировку стека, кроме случаев, когда исключение — SystemExit.
Исключения идентифицируются экземплярами классов. Предложение except выбирается в зависимости от класса экземпляра: в нём должен быть указан класс этого экземпляра или его невиртуальный базовый класс. Экземпляр можно передать обработчику; он может содержать дополнительные сведения об исключительной ситуации.
Примечание
Сообщения исключений не являются частью API Python. Их содержимое может изменяться от одной версии Python к другой без предупреждения, поэтому код, который должен работать в нескольких версиях интерпретатора, не должен на них полагаться.
См. также описание оператора try в разделе Оператор try и оператора raise в разделе Оператор raise.
4.4. Компоненты среды выполнения
4.4.1. Общая модель вычислений
Модель выполнения Python не существует в вакууме. Она работает на хост-компьютере и в его среде выполнения, включая операционную систему (ОС), если она есть. Когда программа выполняется, концептуальные уровни её работы на хост-компьютере выглядят примерно так:
Каждый процесс представляет программу, выполняющуюся на хост-компьютере. Рассматривайте сам процесс как часть программы, отвечающую за данные. Потоки процесса — это часть программы, отвечающая за выполнение. Это различие важно для понимания концептуальной среды выполнения Python.
Процесс, выступая в качестве части, отвечающей за данные, является контекстом выполнения программы. Он состоит главным образом из набора ресурсов, назначенных программе хост-компьютером, включая память, сигналы, дескрипторы файлов, сокеты и переменные окружения.
Процессы изолированы и независимы друг от друга. (То же верно и для хост-компьютеров.) Хост-компьютер управляет доступом процессов к назначенным им ресурсам и координирует взаимодействие между процессами.
Каждый поток представляет фактическое выполнение машинного кода программы с использованием ресурсов, назначенных процессу этой программы. Только хост-компьютер определяет, как и когда происходит это выполнение.
С точки зрения Python, программа всегда запускается ровно с одним потоком. Однако со временем программа может начать выполняться в нескольких потоках одновременно. Не все хост-компьютеры поддерживают несколько потоков на процесс, но большинство поддерживает. В отличие от процессов, потоки одного процесса не изолированы и не независимы друг от друга. В частности, все потоки процесса совместно используют все его ресурсы.
Главное в потоках то, что каждый из них выполняется независимо, одновременно с другими. Это может происходить одновременно лишь концептуально («конкурентно») или физически («параллельно»). В любом случае потоки фактически выполняются с несинхронизированной скоростью.
Примечание
Из-за этой несинхронизированной скорости память процесса не гарантированно остаётся согласованной для кода, выполняющегося в любом из потоков. Поэтому многопоточные программы должны координировать доступ к намеренно совместно используемым ресурсам. Также необходимо проявлять особую осторожность и не обращаться к каким-либо другим ресурсам из нескольких потоков; иначе два потока, выполняющиеся одновременно, могут случайно помешать друг другу при работе с общими данными. Всё это относится как к программам Python, так и к среде выполнения Python.
Необходимость соблюдать это широкое и неструктурированное требование — плата за тот вид необработанной конкурентности, который обеспечивают потоки. Альтернатива такой дисциплине обычно означает необходимость иметь дело с недетерминированными ошибками и повреждением данных.
4.4.2. Модель среды выполнения Python
Для каждой программы Python применяются те же концептуальные уровни, к которым добавляются несколько специфичных для Python уровней данных:
На концептуальном уровне при запуске программа Python выглядит точно так, как показано на этой диаграмме: по одному элементу каждого типа. Среда выполнения может разрастись и включить несколько интерпретаторов, а каждый интерпретатор — несколько состояний потока.
Примечание
Реализация Python не обязана реализовывать уровни среды выполнения раздельно или даже явно. Исключение составляют случаи, когда отдельные уровни непосредственно определены или доступны пользователям, например через модуль threading.
Примечание
Начальный интерпретатор обычно называют «главным». Некоторые реализации Python, например CPython, назначают главному интерпретатору особые роли.
Аналогично, поток хоста, в котором была инициализирована среда выполнения, называют «главным» потоком. Он может отличаться от начального потока процесса, хотя часто это один и тот же поток. В некоторых случаях термин «главный поток» может иметь ещё более узкое значение и относиться к начальному состоянию потока. Среда выполнения Python может возлагать на главный поток определённые обязанности, например обработку сигналов.
В целом среда выполнения Python состоит из глобального состояния среды выполнения, интерпретаторов и состояний потоков. Среда выполнения обеспечивает согласованность всего этого состояния на протяжении всего срока работы, особенно при использовании нескольких потоков хоста.
На концептуальном уровне глобальная среда выполнения — это просто набор интерпретаторов. Хотя в остальном эти интерпретаторы изолированы и независимы друг от друга, они могут совместно использовать некоторые данные или другие ресурсы. Среда выполнения отвечает за безопасное управление этими глобальными ресурсами. Фактический характер и способ управления этими ресурсами зависят от реализации. В конечном счёте внешнее назначение глобальной среды выполнения ограничивается управлением интерпретаторами.
В отличие от неё, «интерпретатор» — это концептуальное представление того, что мы обычно подразумеваем под «полнофункциональной средой выполнения Python». Когда машинный код, выполняющийся в потоке хоста, взаимодействует со средой выполнения Python, он обращается к Python в контексте определённого интерпретатора.
Примечание
Термин «интерпретатор» здесь не означает «интерпретатор байт-кода», который обычно выполняется в потоках и исполняет скомпилированный код Python.
В идеальном мире термин «среда выполнения Python» обозначал бы то, что мы сейчас называем «интерпретатором». Однако термин «интерпретатор» используется как минимум с момента его появления в 1997 году (CPython:a027efa5b).
Каждый интерпретатор полностью инкапсулирует всё состояние, необходимое для работы среды выполнения Python, за исключением состояния, глобального для процесса или специфичного для потока. В частности, состояние интерпретатора сохраняется между его использованиями. Оно включает фундаментальные данные, например sys.modules. Среда выполнения обеспечивает безопасное совместное использование этих данных несколькими потоками, использующими один и тот же интерпретатор.
Реализация Python может поддерживать одновременное использование нескольких интерпретаторов в одном процессе. Они независимы и изолированы друг от друга. Например, у каждого интерпретатора есть собственный sys.modules.
Для управления состоянием среды выполнения, специфичным для потоков, каждый интерпретатор располагает набором состояний потоков — так же, как глобальная среда выполнения содержит набор интерпретаторов. Он может иметь состояния потоков для любого необходимого количества потоков хоста. У одного потока хоста может быть даже несколько состояний потоков, хотя это встречается не так часто.
Концептуально каждое состояние потока содержит все специфичные для потока данные среды выполнения, необходимые интерпретатору для работы в одном потоке хоста. Состояние потока включает текущее возбужденное исключение и стек вызовов Python этого потока. Оно может содержать и другие ресурсы, специфичные для потока.
Примечание
Термин «поток Python» иногда может обозначать состояние потока, но обычно он означает поток, созданный с помощью модуля threading.
На протяжении всего срока существования каждое состояние потока всегда связано ровно с одним интерпретатором и ровно с одним потоком хоста. Оно используется только в этом потоке и с этим интерпретатором.
С одним потоком хоста могут быть связаны несколько состояний потоков — как для разных интерпретаторов, так и для одного и того же интерпретатора. Однако в каждый момент времени поток может использовать только одно из связанных с ним состояний.
Состояния потоков изолированы и независимы друг от друга и не используют общие данные, за исключением случаев, когда они используют общий интерпретатор, его объекты или другие принадлежащие ему ресурсы.
После запуска программы можно создавать новые потоки Python с помощью модуля threading (на платформах и в реализациях Python, поддерживающих потоки). Дополнительные процессы можно создавать с помощью модулей os, subprocess и multiprocessing. Интерпретаторы можно создавать и использовать с помощью модуля interpreters. Короутины (асинхронные функции) можно запускать с помощью asyncio в каждом интерпретаторе, обычно только в одном потоке (часто главном).
Сноски
© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/reference/executionmodel.html