O Modelo de Execução do Python
Todas as outras páginas desta seção - variáveis, fluxo de controle, funções, módulos - baseiam-se em um modelo subjacente de como o Python realmente executa o código.
Esse modelo é mais simples do que parece de fora: nomes são vinculados a objetos dentro de namespaces, e quase tudo que você faz em Python é alguma variação de criar um objeto e vincular um nome a ele.
Esta página é o modelo mental por trás de Variáveis, Tipos e Tipagem Dinâmica, Fluxo de Controle, Funções, Args e Escopo e Módulos, Imports e Pacotes.
Uma vez que você entenda, as regras de escopo, o cache de importação e a armadilha de argumento padrão mutável param de parecer peculiaridades separadas e começam a parecer o mesmo mecanismo aparecendo em lugares diferentes.
Resumo
- A execução de código Python é o ato repetido de vincular nomes a objetos dentro de namespaces - atribuição,
def,classeimportsão todas especializações dessa única operação. - Por que Importa: Assim que você vê nomes e objetos como coisas separadas conectadas por vínculos, as regras de escopo, o estado mutável compartilhado e o cache de importação param de ser casos especiais e se tornam consequências previsíveis de um único mecanismo.
- Conceitos-Chave: vinculação de nome, namespace, escopo (LEGB), identidade do objeto, cache de módulo.
- Quando Usar: Para raciocinar sobre por que duas variáveis são mutadas juntas, por que uma função lê uma variável externa mas não pode reatribuí-la sem
nonlocal, ou por que editar um arquivo de módulo em tempo de execução não afeta uma cópia já importada. - Limitações / Trade-offs: A flexibilidade da vinculação dinâmica de nomes custa algumas garantias estáticas - o tipo de destino de um nome pode mudar em tempo de execução, e ferramentas só podem aproximar o que uma linguagem totalmente estática verifica gratuitamente.
- Tópicos Relacionados: semântica de referência e mutabilidade, escopo de função e closures, o sistema de importação, o modelo de objeto do CPython.
Fundamentos
A mudança de pensamento mais útil sobre Python é parar de chamar x = 5 de "armazenar 5 em x" e começar a chamar de "vincular o nome x a um objeto inteiro 5".
O objeto existe independentemente, com seu próprio tipo, valor e identidade; o nome é apenas um rótulo apontando para ele, armazenado em um namespace.
Um namespace é, concretamente, um dicionário que mapeia nomes (strings) a objetos - globals() e locals() dão uma olhada direta em dois deles.
x = [1, 2, 3]
print(globals()["x"] is x) # True - o namespace do módulo é realmente um dictAtribuição (x = ...), def, class e import se reduzem à mesma primitiva: construir (ou localizar) um objeto, e então vincular um nome a ele no namespace atual.
É por isso que uma função e um módulo ambos têm atributos que você pode inspecionar - um objeto de função tem um __name__, um __doc__, um __defaults__; um objeto de módulo tem quaisquer nomes que seu código de nível superior vinculou.
Uma analogia útil é um sistema de arquivamento: objetos são as pastas físicas armazenadas, e nomes são notas adesivas apontando para elas - mover uma nota adesiva (reatribuir um nome) nunca toca na pasta, mas escrever dentro de uma pasta (mutar um objeto) é visível para todas as notas adesivas que apontam para ela.
Mecanismos e Interações
Todo namespace existe dentro de um escopo específico, e o Python resolve um nome nu buscando escopos em uma ordem fixa: Local, Enclosing (Envolvente), Global, Built-in (Embutido) - LEGB.
Cada chamada de função cria um novo namespace local (um frame), que é por que chamadas recursivas não sobrescrevem as variáveis umas das outras, mesmo que compartilhem o mesmo objeto de código.
nonlocal e global existem porque ler um nome busca para fora através de LEGB automaticamente, mas atribuir a um nome tem como padrão criá-lo localmente - essas duas palavras-chave dizem ao Python "pule a criação local, reatribua o nome que já existe mais adiante".
def make_counter():
count = 0
def increment():
nonlocal count # sem isso, count += 1 levanta UnboundLocalError
count += 1
return count
return increment
tick = make_counter()
print(tick(), tick()) # 1 2 - o estado vive no frame envolvente, não globalmenteO sistema import adiciona um mecanismo de cache sobre o mesmo modelo: o primeiro import mymodule executa o código de nível superior desse arquivo exatamente uma vez, constrói um objeto de módulo a partir do namespace resultante e o armazena em sys.modules; qualquer import mymodule posterior em qualquer lugar do processo apenas busca esse objeto em cache em vez de reexecutar o arquivo.
Este único fato explica duas surpresas comuns de uma vez: editar o código-fonte de um módulo em tempo de execução não tem efeito até que o processo seja reiniciado ou o módulo seja explicitamente recarregado, e importar um módulo com efeitos colaterais (como abrir uma conexão no nível superior) só incorre nesse custo uma vez, não importa quantos arquivos o importem.
Valores de argumento padrão interagem com este modelo de uma forma que engana quase todo mundo uma vez: def f(items=[]) avalia [] uma única vez, quando a instrução def é executada, e armazena esse único objeto de lista em __defaults__ da função - cada chamada que omite items compartilha esse mesmo objeto, que é por que mutá-lo dentro da função vaza estado entre chamadas.
Considerações Avançadas e Aplicações
CPython compila o código-fonte em bytecode (visualizável através do módulo dis) antes que uma máquina virtual o execute frame a frame, que é por que Python é geralmente chamado de linguagem interpretada, mesmo que exista uma etapa de compilação real.
Durante a maior parte da história do CPython, o Global Interpreter Lock (GIL) serializou a execução de bytecode entre threads em um único processo, o que significa que código multi-thread limitado por CPU raramente roda mais rápido que código single-thread - uma consequência direta de como o modelo de objeto de contagem de referências gerencia a segurança da memória.
Python 3.13 introduziu uma build free-threaded experimental (PEP 703) que pode rodar sem o GIL, e Python 3.14 continua a estabilizá-la como um modo de build oficial (embora ainda opcional) - uma evolução genuína deste modelo de execução, não apenas um botão de ajuste.
| Modelo de execução | Força | Fraqueza | Melhor ajuste |
|---|---|---|---|
| Bytecode CPython + GIL (build padrão) | Contagem de referência simples e segura; compatibilidade com vasto ecossistema | Uma thread executa bytecode Python por vez por processo | Scripts de propósito geral, serviços limitados por I/O, a maioria do código de produção hoje |
| Build free-threaded CPython (3.13+, opcional) | Verdadeiro paralelismo multi-core para trabalho puro de CPU em Python | Mais novo, alguma incompatibilidade com extensões C, sobrecarga adicional de bloqueio por objeto | Cargas de trabalho limitadas por CPU que não podem usar multiprocessamento ou código nativo |
| Isolamento de Multiprocessamento / subprocesso | Contorna o GIL inteiramente através de processos separados | Maior uso de memória, comunicação inter-processos mais lenta | Trabalho limitado por CPU hoje, em qualquer build Python suportado |
A coleta de lixo segue a mesma história de objeto e referência: CPython usa principalmente contagem de referência (um objeto é liberado no instante em que sua contagem atinge zero), apoiado por um coletor cíclico que periodicamente varre ciclos de referência que a contagem sozinha não consegue capturar, como dois objetos que apontam um para o outro.
Entender namespaces como dicionários também explica por que dir(), vars() e getattr() funcionam uniformemente em módulos, instâncias e classes - eles estão apenas inspecionando ou consultando um objeto baseado em namespace em vez de três recursos de linguagem não relacionados.
Equívocos Comuns
- "Variáveis Python são como variáveis C - slots de armazenamento tipados." Um nome não tem tipo próprio; o objeto ao qual ele está vinculado tem um tipo, e o mesmo nome pode ser reatribuído a um tipo de objeto completamente diferente a qualquer momento.
- "Atribuição copia o valor." Atribuição vincula um nome ao objeto existente; dois nomes podem apontar para o mesmo objeto mutável, que é por que
y = x; y.append(1)também muda o quexvê para objetos mutáveis. - "Todo
importreexecuta o código do módulo." Apenas o primeiro import por processo faz isso; todo import subsequente do mesmo nome de módulo retorna o objeto em cache desys.modules. - "Uma função aninhada pode ler e escrever qualquer variável envolvente livremente." A leitura percorre LEGB automaticamente, mas atribuir dentro de uma função aninhada cria um novo nome local por padrão, a menos que
nonlocaldiga o contrário. - "Argumentos padrão mutáveis são reavaliados em cada chamada." A expressão padrão é executada uma vez, no momento do
def, e o objeto resultante é reutilizado em cada chamada que omite esse argumento.
FAQs
O que realmente significa dizer que um nome está "vinculado" a um objeto?
Significa que uma entrada mapeando esse nome (uma string) para esse objeto agora existe em algum namespace - uma estrutura semelhante a um dicionário, como os globais de um módulo, os locais de uma função ou um corpo de classe.
Por que `x = y` não cria uma cópia de `y` para `x`?
Porque atribuição nunca copia dados - ela apenas cria ou atualiza um vínculo nome-para-objeto, então x e y acabam apontando para o mesmo objeto quando o lado direito já é uma referência de objeto.
O que é LEGB, exatamente?
É a ordem fixa que o Python busca ao resolver um nome nu: Local (o frame da função atual), Enclosing (qualquer frame de função externa), Global (o namespace do módulo) e Built-in (nomes embutidos do Python como len ou print).
Por que atribuir a uma variável dentro de uma função às vezes levanta `UnboundLocalError`?
Se o corpo da função atribui a esse nome em qualquer lugar, o Python o trata como local para a função inteira, então lê-lo antes dessa atribuição falha - global ou nonlocal são necessários para reatribuir um nome externo em vez de sombreá-lo.
Um módulo Python é basicamente um dicionário?
Funcionalmente, sim - o namespace de um objeto de módulo (tudo que você pode acessar como modulo.nome) é suportado por um dicionário que é preenchido executando o código de nível superior do módulo uma vez.
O que acontece se eu importar o mesmo módulo de dois arquivos diferentes?
Python executa o código de nível superior do módulo apenas na primeira vez; cada import subsequente em qualquer lugar do mesmo processo recupera o objeto de módulo em cache idêntico de sys.modules em vez de reexecutar qualquer coisa.
Por que a armadilha de argumento padrão mutável está conectada a este modelo de execução?
Porque uma instrução def executa suas expressões de valor padrão exatamente uma vez e armazena o objeto resultante na função - cada chamada que omite esse argumento reutiliza o mesmo objeto, então mutá-lo acumula estado entre chamadas.
O que é um namespace, concretamente?
Um mapeamento de nomes para objetos - na prática, a maioria dos namespaces (globais de módulos, locais de funções, corpos de classes) são implementados como dicionários que você pode inspecionar com globals(), locals() ou vars().
Python compila código antes de executá-lo?
Sim - CPython compila o código-fonte em bytecode primeiro, então uma máquina virtual executa esse bytecode frame a frame; é por isso que geralmente é descrito como interpretado, mesmo que ocorra uma etapa de compilação real.
Por que apenas uma thread pode executar bytecode Python por vez em um processo CPython normal?
O Global Interpreter Lock (GIL) serializa a execução de bytecode para manter a contagem de referência segura sem precisar de um lock em cada objeto individual; Python 3.13+ oferece uma build free-threaded experimental que remove essa restrição ao custo de sobrecarga adicional de bloqueio por objeto.
Como o Python decide quando liberar a memória de um objeto?
Principalmente através da contagem de referência - no momento em que a contagem de referência de um objeto atinge zero, ele é liberado imediatamente - com um coletor de lixo cíclico periódico lidando com o caso mais raro de objetos que se referenciam em um ciclo.
Por que entender nomes versus objetos é mais importante do que memorizar sintaxe?
Porque as regras de escopo, bugs de mutação compartilhada, comportamento de cache de importação e a armadilha de padrão mutável são todos sintomas de aparência separada do exato mesmo mecanismo subjacente, então entender o mecanismo uma vez explica todos eles em vez de memorizar quatro regras não relacionadas.
Relacionados
- Variáveis, Tipos e Tipagem Dinâmica - semântica de referência e identidade de objeto em profundidade
- Fluxo de Controle - como construtos de ramificação e loop executam dentro deste mesmo modelo
- Funções, Args e Escopo - escopo LEGB e vinculação de argumentos na prática
- Módulos, Imports e Pacotes - o cache de importação e namespaces de pacotes de perto
- Fundamentos do Python - um tour prático da sintaxe que este modelo sustenta
Versões da Stack: Esta página foi escrita para Python 3.14 (estável) e Python 3.13 (manutenção), incluindo as notas sobre a build free-threaded introduzidas em 3.13 e estabilizadas ainda mais em 3.14.