4. Modelo de execução¶
4.1. Estrutura de um programa¶
Um programa Python é construído a partir de blocos de código. Um bloco é um pedaço do texto do programa Python que é executado como uma unidade. A seguir estão os blocos: um módulo, um corpo de função e uma definição de classe. Cada comando digitado interativamente é um bloco. Um arquivo de script (um arquivo fornecido como entrada padrão para o interpretador ou especificado como argumento de linha de comando para o interpretador) é um bloco de código. Um comando de script (um comando especificado na linha de comando do interpretador com a opção -c
) é um bloco de código. Um módulo executado sobre um script de nível superior (como o módulo __main__
) a partir da linha de comando usando um argumento -m
também é um bloco de código. O argumento da string passado para as funções embutidas eval()
e exec()
é um bloco de código.
Um bloco de código é executado em um quadro de execução. Um quadro contém algumas informações administrativas (usadas para depuração) e determina onde e como a execução continua após a conclusão do bloco de código.
4.2. Nomeação e ligação¶
4.2.1. Ligação de nomes¶
Nomes referem-se a objetos. Os nomes são introduzidos por operações de ligação de nomes.
As seguintes construções ligam nomes:
parâmetros formais para funções,
definições de classe,
definições de função,
expressões de atribuição,
alvos que são identificadores se ocorrerem em uma atribuição:
instruções
import
.instruções
type
.
A instrução import
no formato from ... import *
liga todos os nomes definidos no módulo importado, exceto aqueles que começam com um sublinhado. Este formulário só pode ser usado no nível do módulo.
Um alvo ocorrendo em uma instrução del
também é considerado ligado a esse propósito (embora a semântica real seja para desligar do nome).
Cada atribuição ou instrução de importação ocorre dentro de um bloco definido por uma definição de classe ou função ou no nível do módulo (o bloco de código de nível superior).
Se um nome está ligado a um bloco, é uma variável local desse bloco, a menos que declarado como nonlocal
ou global
. Se um nome está ligado a nível do módulo, é uma variável global. (As variáveis do bloco de código do módulo são locais e globais.) Se uma variável for usada em um bloco de código, mas não definida lá, é uma variável livre.
Cada ocorrência de um nome no texto do programa se refere à ligação daquele nome estabelecido pelas seguintes regras de resolução de nome.
4.2.2. Resolução de nomes¶
O escopo define a visibilidade de um nome dentro de um bloco. Se uma variável local é definida em um bloco, seu escopo inclui esse bloco. Se a definição ocorrer em um bloco de função, o escopo se estende a quaisquer blocos contidos no bloco de definição, a menos que um bloco contido introduza uma ligação diferente para o nome.
Quando um nome é usado em um bloco de código, ele é resolvido usando o escopo envolvente mais próximo. O conjunto de todos esses escopos visíveis a um bloco de código é chamado de ambiente do bloco.
Quando um nome não é encontrado, uma exceção NameError
é levantada. Se o escopo atual for um escopo de função e o nome se referir a uma variável local que ainda não foi associada a um valor no ponto onde o nome é usado, uma exceção UnboundLocalError
é levantada. UnboundLocalError
é uma subclasse de NameError
.
Se a operação de ligação de nomes ocorre dentro de um bloco de código, todos os usos do nome dentro do bloco são tratadas como referências para o bloco atual. Isso pode. Isso pode levar a erros quando um nome é usado em um bloco antes de ser vinculado. Esta regra é sutil. Python carece de declarações e permite que as operações de ligação de nomes ocorram em qualquer lugar dentro de um bloco de código. As variáveis locais de um bloco de código podem ser determinadas pela varredura de todo o texto do bloco para operações de ligação de nome. Veja o FAQ sobre UnboundLocalError para exemplos.
Se a instrução global
ocorrer dentro de um bloco, todos os usos dos nomes especificados na instrução referem-se às ligações desses nomes no espaço de nomes de nível superior. Os nomes são resolvidos no espaço de nomes de nível superior pesquisando o espaço de nomes global, ou seja, o espaço de nomes do módulo que contém o bloco de código, e o espaço de nomes embutido, o espaço de nomes do módulo builtins
. O espaço de nomes global é pesquisado primeiro. Se os nomes não forem encontrados lá, o espaço de nomes embutidos será pesquisado em seguida. Se os nomes também não forem encontrados no espaço de nomes embutido, novas variáveis são criadas no espaço de nomes global. A instrução global deve preceder todos os usos dos nomes listados.
A instrução global
tem o mesmo escopo que uma operação de ligação de nome no mesmo bloco. Se o escopo mais próximo de uma variável livre contiver uma instrução global, a variável livre será tratada como global.
A instrução nonlocal
faz com que os nomes correspondentes se refiram a variáveis previamente vinculadas no escopo da função delimitadora mais próxima. A exceção SyntaxError
é levantada em tempo de compilação se o nome fornecido não existir em nenhum escopo de função delimitador. Parâmetros de tipo não podem ser vinculadas novamente com a instrução nonlocal
.
O espaço de nomes de um módulo é criado automaticamente na primeira vez que um módulo é importado. O módulo principal de um script é sempre chamado de __main__
.
Blocos de definição de classe e argumentos para exec()
e eval()
são especiais no contexto de resolução de nome. Uma definição de classe é uma instrução executável que pode usar e definir nomes. Essas referências seguem as regras normais para resolução de nome, com exceção de que variáveis locais não vinculadas são pesquisadas no espaço de nomes global global. O espaço de nomes global da definição de classe se torna o dicionário de atributos da classe. O escopo dos nomes definidos em um bloco de classe é limitado ao bloco de classe; ele não se estende aos blocos de código de métodos. Isso inclui compreensões e expressões geradoras, mas não inclui escopos de anotação, que têm acesso a seus escopos de classe delimitadores. Isso significa que o seguinte falhará:
class A:
a = 42
b = list(a + i for i in range(10))
Porém, o seguinte vai funcionar:
class A:
type Alias = Nested
class Nested: pass
print(A.Alias.__value__) # <type 'A.Nested'>
4.2.3. Escopos de anotação¶
As instruções de anotações, listas de parâmetros de tipo e type
introduzem escopos de anotação, que se comportam principalmente como escopos de função, mas com algumas exceções discutidas abaixo.
Os escopos de anotação são usados nos seguintes contextos:
Listas de parâmetros de tipo para apelidos de tipo genérico.
Listas de parâmetros de tipo para funções genéricas. As anotações de uma função genérica são executadas dentro do escopo de anotação, mas seus padrões e decoradores não.
Listas de parâmetros de tipo para classes genéricas. As classes base e argumentos nomeados de uma classe genérica são executadas dentro do escopo de anotação, mas seus decoradores não.
Os limites, restrições e valores padrão para parâmetros de tipo (avaliados preguiçosamente).
O valor dos apelidos de tipo (avaliado preguiçosamente).
Escopos de anotação diferenciam-se de escopos de função nas seguintes formas:
Os escopos de anotação têm acesso ao espaço de nomes da classe delimitadora. Se um escopo de anotação estiver imediatamente dentro de um escopo de classe ou dentro de outro escopo de anotação que esteja imediatamente dentro de um escopo de classe, o código no escopo de anotação poderá usar nomes definidos no escopo de classe como se fosse executado diretamente no corpo da classe. Isto contrasta com funções regulares definidas dentro de classes, que não podem acessar nomes definidos no escopo da classe.
Expressões em escopos de anotação não podem conter expressões
yield
,yield from
,await
ou:= 1
. (Essas expressões são permitidas em outros escopos contidos no escopo de anotação.)Nomes definidos em escopos de anotação não podem ser vinculados novamente com instruções
nonlocal
em escopos internos. Isso inclui apenas parâmetros de tipo, pois nenhum outro elemento sintático que pode aparecer nos escopos de anotação pode introduzir novos nomes.Embora os escopos de anotação tenham um nome interno, esse nome não é refletido no nome qualificado dos objetos definidos dentro do escopo. Em vez disso, o
__qualname__
de tais objetos é como se o objeto fosse definido no escopo delimitador.
Adicionado na versão 3.12: Escopos de anotação foram introduzidos no Python 3.12 como parte da PEP 695.
Alterado na versão 3.13: Os escopos de anotação também são usados para padrões de parâmetros de tipo, conforme introduzido pela PEP 696.
4.2.4. Avaliação preguiçosa¶
A maioria dos escopos de anotação são avaliados preguiçosamente. Isso inclui anotações, os valores de apelidos de tipo criados por meio da instrução type
e os limites, restrições e valores padrão de tipos variáveis criados por meio da sintaxe de parâmetro de tipo sintaxe de parâmetro de tipo. Isso significa que eles não são avaliados quando o apelido de tipo ou a tipo variável é criado, ou quando o objeto que contém as anotações é criado. Em vez disso, eles são avaliados apenas quando necessário, por exemplo, quando o atributo __value__
em um apelido de tipo é acessado.
Exemplo:
>>> 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
Aqui a exceção é levantada apenas quando o atributo __value__
do apelido de tipo ou o atributo __bound__
da variável de tipo é acessado.
Esse comportamento é útil principalmente para referências a tipos que ainda não foram definidos quando o alias de tipo ou variável de tipo é criado. Por exemplo, a avaliação preguiçosa permite a criação de apelidos de tipo mutuamente recursivos:
from typing import Literal
type SimpleExpr = int | Parenthesized
type Parenthesized = tuple[Literal["("], Expr, Literal[")"]]
type Expr = SimpleExpr | tuple[SimpleExpr, Literal["+", "-"], Expr]
Valores avaliados preguiçosamente são avaliados em escopo de anotação, o que significa que os nomes que aparecem dentro do valor avaliado preguiçosamente são pesquisados como se fossem usados no escopo imediatamente envolvente.
Adicionado na versão 3.12.
4.2.5. Builtins e execução restrita¶
Os usuários não devem tocar em __builtins__
; é estritamente um detalhe de implementação. Usuários que desejam substituir valores no espaço de nomes interno devem import
o módulo builtins
e modificar seus atributos apropriadamente.
O espaço de nomes builtins associado com a execução de um bloco de código é encontrado procurando o nome __builtins__
em seu espaço de nomes global; este deve ser um dicionário ou um módulo (no último caso, o dicionário do módulo é usado). Por padrão, quando no módulo __main__
, __builtins__
é o módulo embutido builtins
; quando em qualquer outro módulo, __builtins__
é um apelido para o dicionário do próprio módulo builtins
.
4.2.6. Interação com recursos dinâmicos¶
A resolução de nome de variáveis livres ocorre em tempo de execução, não em tempo de compilação. Isso significa que o código a seguir imprimirá 42:
i = 10
def f():
print(i)
i = 42
f()
As funções eval()
e exec()
não têm acesso ao ambiente completo para resolução de nome. Os nomes podem ser resolvidos nos espaços de nomes locais e globais do chamador. Variáveis livres não são resolvidas no espaço de nomes mais próximo, mas no espaço de nomes global. [1] As funções exec()
e eval()
possuem argumentos opcionais para substituir o espaço de nomes global e local. Se apenas um espaço de nomes for especificado, ele será usado para ambos.
4.3. Exceções¶
As exceções são um meio de romper o fluxo normal de controle de um bloco de código para tratar erros ou outras condições excepcionais. Uma exceção é levantada no ponto em que o erro é detectado; ele pode ser tratado pelo bloco de código circundante ou por qualquer bloco de código que invocou direta ou indiretamente o bloco de código onde ocorreu o erro.
O interpretador Python levanta uma exceção quando detecta um erro em tempo de execução (como divisão por zero). Um programa Python também pode levantar explicitamente uma exceção com a instrução raise
. Os tratadores de exceção são especificados com a instrução try
… except
. A cláusula finally
de tal declaração pode ser usada para especificar o código de limpeza que não trata a exceção, mas é executado se uma exceção ocorreu ou não no código anterior.
Python usa o modelo de “terminação” da manipulação de erros: um manipulador de exceção pode descobrir o que aconteceu e continuar a execução em um nível externo, mas não pode reparar a causa do erro e tentar novamente a operação com falha (exceto reinserindo a parte incorreta de código de cima).
Quando uma exceção não é manipulada, o interpretador encerra a execução do programa ou retorna ao seu laço principal interativo. Em ambos os casos, ele exeibe um traceback (situação da pilha de execução), exceto quando a exceção é SystemExit
.
As exceções são identificadas por instâncias de classe. A cláusula except
é selecionada dependendo da classe da instância: ela deve referenciar a classe da instância ou uma classe base não-virtual dela. A instância pode ser recebida pelo manipulador e pode conter informações adicionais sobre a condição excepcional.
Nota
As mensagens de exceção não fazem parte da API do Python. Seu conteúdo pode mudar de uma versão do Python para outra sem aviso e não deve ser invocado pelo código que será executado em várias versões do interpretador.
Veja também a descrição da declaração try
na seção A instrução try e a instrução raise
na seção A instrução raise.
4.4. Runtime Components¶
4.4.1. General Computing Model¶
Python’s execution model does not operate in a vacuum. It runs on a host machine and through that host’s runtime environment, including its operating system (OS), if there is one. When a program runs, the conceptual layers of how it runs on the host look something like this:
host machineprocess (global resources)thread (runs machine code)
Each process represents a program running on the host. Think of each process itself as the data part of its program. Think of the process’ threads as the execution part of the program. This distinction will be important to understand the conceptual Python runtime.
The process, as the data part, is the execution context in which the program runs. It mostly consists of the set of resources assigned to the program by the host, including memory, signals, file handles, sockets, and environment variables.
Processes are isolated and independent from one another. (The same is true for hosts.) The host manages the process’ access to its assigned resources, in addition to coordinating between processes.
Each thread represents the actual execution of the program’s machine code, running relative to the resources assigned to the program’s process. It’s strictly up to the host how and when that execution takes place.
From the point of view of Python, a program always starts with exactly one thread. However, the program may grow to run in multiple simultaneous threads. Not all hosts support multiple threads per process, but most do. Unlike processes, threads in a process are not isolated and independent from one another. Specifically, all threads in a process share all of the process’ resources.
The fundamental point of threads is that each one does run independently, at the same time as the others. That may be only conceptually at the same time (“concurrently”) or physically (“in parallel”). Either way, the threads effectively run at a non-synchronized rate.
Nota
That non-synchronized rate means none of the process’ memory is guaranteed to stay consistent for the code running in any given thread. Thus multi-threaded programs must take care to coordinate access to intentionally shared resources. Likewise, they must take care to be absolutely diligent about not accessing any other resources in multiple threads; otherwise two threads running at the same time might accidentally interfere with each other’s use of some shared data. All this is true for both Python programs and the Python runtime.
The cost of this broad, unstructured requirement is the tradeoff for the kind of raw concurrency that threads provide. The alternative to the required discipline generally means dealing with non-deterministic bugs and data corruption.
4.4.2. Python Runtime Model¶
The same conceptual layers apply to each Python program, with some extra data layers specific to Python:
host machineprocess (global resources)Python global runtime (state)Python interpreter (state)thread (runs Python bytecode and “C-API”)Python thread state
At the conceptual level: when a Python program starts, it looks exactly like that diagram, with one of each. The runtime may grow to include multiple interpreters, and each interpreter may grow to include multiple thread states.
Nota
A Python implementation won’t necessarily implement the runtime
layers distinctly or even concretely. The only exception is places
where distinct layers are directly specified or exposed to users,
like through the threading
module.
Nota
The initial interpreter is typically called the “main” interpreter. Some Python implementations, like CPython, assign special roles to the main interpreter.
Likewise, the host thread where the runtime was initialized is known as the “main” thread. It may be different from the process’ initial thread, though they are often the same. In some cases “main thread” may be even more specific and refer to the initial thread state. A Python runtime might assign specific responsibilities to the main thread, such as handling signals.
As a whole, the Python runtime consists of the global runtime state, interpreters, and thread states. The runtime ensures all that state stays consistent over its lifetime, particularly when used with multiple host threads.
The global runtime, at the conceptual level, is just a set of interpreters. While those interpreters are otherwise isolated and independent from one another, they may share some data or other resources. The runtime is responsible for managing these global resources safely. The actual nature and management of these resources is implementation-specific. Ultimately, the external utility of the global runtime is limited to managing interpreters.
In contrast, an “interpreter” is conceptually what we would normally think of as the (full-featured) “Python runtime”. When machine code executing in a host thread interacts with the Python runtime, it calls into Python in the context of a specific interpreter.
Nota
The term “interpreter” here is not the same as the “bytecode interpreter”, which is what regularly runs in threads, executing compiled Python code.
In an ideal world, “Python runtime” would refer to what we currently call “interpreter”. However, it’s been called “interpreter” at least since introduced in 1997 (CPython:a027efa5b).
Each interpreter completely encapsulates all of the non-process-global,
non-thread-specific state needed for the Python runtime to work.
Notably, the interpreter’s state persists between uses. It includes
fundamental data like sys.modules
. The runtime ensures
multiple threads using the same interpreter will safely
share it between them.
A Python implementation may support using multiple interpreters at the
same time in the same process. They are independent and isolated from
one another. For example, each interpreter has its own
sys.modules
.
For thread-specific runtime state, each interpreter has a set of thread states, which it manages, in the same way the global runtime contains a set of interpreters. It can have thread states for as many host threads as it needs. It may even have multiple thread states for the same host thread, though that isn’t as common.
Each thread state, conceptually, has all the thread-specific runtime data an interpreter needs to operate in one host thread. The thread state includes the current raised exception and the thread’s Python call stack. It may include other thread-specific resources.
Nota
The term “Python thread” can sometimes refer to a thread state, but
normally it means a thread created using the threading
module.
Each thread state, over its lifetime, is always tied to exactly one interpreter and exactly one host thread. It will only ever be used in that thread and with that interpreter.
Multiple thread states may be tied to the same host thread, whether for different interpreters or even the same interpreter. However, for any given host thread, only one of the thread states tied to it can be used by the thread at a time.
Thread states are isolated and independent from one another and don’t share any data, except for possibly sharing an interpreter and objects or other resources belonging to that interpreter.
Once a program is running, new Python threads can be created using the
threading
module (on platforms and Python implementations that
support threads). Additional processes can be created using the
os
, subprocess
, and multiprocessing
modules.
Interpreters can be created and used with the
interpreters
module. Coroutines (async) can
be run using asyncio
in each interpreter, typically only
in a single thread (often the main thread).
Notas de rodapé