Документация Python неофициальный перевод
Содержание страницы

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

4.1. Структура программыStructure of a program

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

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

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

4.2.1. Связывание имёнBinding of names

Имена ссылаются на объекты. Имена вводятся операциями связывания имён.

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

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

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

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

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

  • targets that are identifiers if occurring in an assignment:

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

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

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

  • операторы import.

  • операторы type.

  • type parameter lists.

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

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

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

If a name is bound in a block, it is a local variable of that block, unless declared as nonlocal or global. If a name is bound at the module level, it is a global variable. (The variables of the module code block are local and global.) If a variable is used in a code block but not defined there, it is a free variable.

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

4.2.2. Разрешение имёнResolution of names

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

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

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

Если операция связывания имени встречается в любом месте блока кода, все использования этого имени в блоке считаются ссылками на текущий блок. Это может приводить к ошибкам, когда имя используется в блоке до его связывания. Правило это тонкое. В Python нет объявлений, и операции связывания имени могут встречаться в любом месте блока кода. Локальные переменные блока кода можно определить, просмотрев весь текст блока на предмет операций связывания имён. Примеры см. в FAQ про 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. Области видимости аннотацийAnnotation scopes

Аннотации, списки параметров типа и оператор type вводят области видимости аннотаций, которые ведут себя в основном как области видимости функций, но с некоторыми исключениями, описанными ниже.

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

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

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

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

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

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

Добавлено в версии 3.12: Области видимости аннотаций были введены в Python 3.12 в рамках PEP 695.

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

Изменено в версии 3.14: Области видимости аннотаций теперь также используются для аннотаций, как указано в PEP 649 и PEP 749.

4.2.4. Ленивое вычислениеLazy evaluation

Большинство областей видимости аннотаций вычисляются лениво. Это включает аннотации, значения псевдонимов типов, созданных с помощью оператора 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. Встроенные объекты и ограниченное выполнениеBuiltins and restricted execution

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

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

4.2.6. Взаимодействие с динамическими возможностямиInteraction with dynamic features

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

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

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

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

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

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

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

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

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

Примечание

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

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

4.4. Компоненты времени выполненияRuntime Components

4.4.1. Общая модель вычисленийGeneral Computing Model

Модель выполнения Python не работает в вакууме. Она выполняется на физической машине и через среду выполнения этой машины, включая её операционную систему (ОС), если таковая имеется. Когда программа работает, концептуальные уровни того, как она выполняется на машине, выглядят примерно так:

физическая машина
процесс (глобальные ресурсы)
поток (выполняет машинный код)

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

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

Процессы изолированы и независимы друг от друга. (То же верно для машин.) Машина управляет доступом процесса к своим назначенным ресурсам, а также координирует взаимодействие между процессами.

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

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

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

Примечание

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

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

4.4.2. Модель выполнения PythonPython Runtime Model

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

физическая машина
процесс (глобальные ресурсы)
Глобальная среда выполнения Python (состояние)
Интерпретатор Python (состояние)
Поток (выполняет байт-код Python и «C-API»)
Состояние потока Python

На концептуальном уровне: когда программа на Python запускается, она выглядит в точности как эта диаграмма, с одним каждого типа. Среда выполнения может разрастаться, включая несколько интерпретаторов, и каждый интерпретатор может разрастаться, включая несколько состояний потока.

Примечание

Реализация Python не обязательно должна реализовывать слои среды выполнения явно или даже конкретно. Единственное исключение – места, где различные слои напрямую указаны или открыты пользователям, например, через модуль threading.

Примечание

Начальный интерпретатор обычно называется «главным» (main). Некоторые реализации Python, такие как CPython, назначают главному интерпретатору особые роли.

Аналогично, поток хоста, в котором была инициализирована среда выполнения, называется «главным» (main). Он может отличаться от начального потока процесса, хотя часто они совпадают. В некоторых случаях «главный поток» может быть ещё более специфичным и относиться к начальному состоянию потока. Среда выполнения 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 в каждом интерпретаторе, обычно только в одном потоке (часто главном потоке).

Сноски