Escopo de variáveis em Python, closures e a regra LEGB
Este tutorial foi traduzido do original em inglês.Read in EnglishLeer en españolЧитать на русскомอ่านภาษาไทยAvisar sobre um erro de tradução
Imagine uma casa com vários cômodos. Cada cômodo tem seus próprios objetos: um abajur no criado-mudo do quarto, uma faca na gaveta da cozinha. Você pode usar o que está no cômodo onde você está, e pode ir até o corredor pegar algo que é de todos. Mas não dá para pegar algo no quarto fechado de outra pessoa sem abrir a porta antes.
As variáveis do Python funcionam do mesmo jeito. Toda variável vive em um escopo: uma região específica do código onde essa variável existe e pode ser usada. Entender escopo é a chave para escrever funções limpas, que não sobrescrevem sem querer os dados umas das outras.
Neste tutorial, você vai aprender os quatro níveis de escopo do Python (a regra LEGB), como usar as palavras-chave global e nonlocal e como as closures permitem que funções internas se lembrem de valores da função que as contém. No final, você vai conseguir depurar com confiança os erros mais confusos de "variável não encontrada".
O que é escopo local?
Quando você cria uma variável dentro de uma função, ela só existe dentro dessa função. Isso se chama escopo local. Pense nele como um quarto privado: nada fora da função consegue ver nem mexer na variável.
Hello from inside!
A variável message é criada dentro de greet(), então ela é local a essa função. Quando greet() termina, message deixa de existir. Se você tentar usá-la fora, o Python gera um NameError.
Cada chamada de função cria variáveis locais novas, do zero. Elas não passam de uma chamada para a outra:
x is 1 x is 1 x is 1
O que é escopo global?
Uma variável criada fora de qualquer função vive no escopo global. É como um objeto no corredor da sua casa: todos os cômodos conseguem vê-lo.
Language: Python Welcome to Python!
As duas funções conseguem ler a variável global language. Mas o que acontece se você tentar alterá-la dentro de uma função?
(sem saída)
Isso gera um UnboundLocalError. O Python vê counter = ... dentro da função e assume que counter é uma variável local. Mas aí o lado direito tenta ler counter antes que ela tenha recebido um valor localmente. O Python se confunde.
A regra LEGB: como o Python encontra variáveis
Quando você usa o nome de uma variável, o Python procura em quatro lugares, numa ordem específica. Isso se chama regra LEGB:
print, len, range.O Python verifica nessa ordem: primeiro L, depois E, depois G, depois B. Ele para na primeira correspondência. Se não encontrar nada em nenhum deles, você recebe um NameError.
local x enclosing x global x
Cada print(x) encontra um x diferente, porque procura a partir do seu próprio escopo, de dentro para fora. A função interna encontra primeiro o seu x local e para de procurar.
A palavra-chave global
Se você precisa modificar uma variável global de dentro de uma função, precisa avisar o Python explicitamente com a palavra-chave global. É como ganhar uma chave especial para entrar no corredor e mudar as coisas de lugar.
Score is now 10 Score is now 35 Score is now 40 Final score: 40
A declaração global score diz ao Python: "Não estou criando uma nova variável local. Quero usar a global." Agora a atribuição score = score + points funciona corretamente.
total = 0
def add(n):
global total
total = total + n
add(5)
add(3)
print(total) # 8def add(current, n):
return current + n
total = 0
total = add(total, 5)
total = add(total, 3)
print(total) # 8Escopo envolvente e closures
Quando você define uma função dentro de outra função, a função interna pode acessar as variáveis da função externa. O escopo dessa função externa se chama escopo envolvente (enclosing scope): é o "E" de LEGB.
Hello, Alice! Hello, Bob! Howdy, Alice!
Algo notável acontece aqui. make_greeting("Hello") termina de executar e retorna a função interna greet. Normalmente, a variável local greeting seria destruída. Mas a função interna ainda precisa dela!
O Python mantém greeting viva porque greet faz referência a ela. Essa combinação de uma função com as variáveis que ela lembra do seu escopo envolvente se chama closure.
Closures são úteis para criar funções especializadas. Veja um exemplo prático, uma fábrica de multiplicadores:
10 15 200
A palavra-chave nonlocal
Assim como global permite modificar variáveis globais, nonlocal permite modificar variáveis do escopo envolvente. Sem ela, atribuir um valor à variável criaria uma nova variável local.
1 2 3
Sem nonlocal count, a linha count = count + 1 geraria um UnboundLocalError, o mesmo problema que vimos com as variáveis globais. A palavra-chave nonlocal diz ao Python para usar a variável com esse nome que está na função envolvente mais próxima.
Esse padrão é poderoso. A variável count fica completamente escondida do mundo exterior. Nenhum código fora de make_counter consegue acessá-la ou mexer nela. Isso é uma forma de encapsulamento: manter os dados privados.
Erros comuns de escopo e como corrigi-los
Estes são os bugs de escopo que mais pegam os desenvolvedores Python. Conhecer esses padrões vai poupar horas de depuração.
Erro 1: sombrear uma variável global
Inside: ['date'] Outside: ['apple', 'banana', 'cherry']
O items = [] dentro da função cria uma variável local totalmente nova, que sombreia (esconde) a global. A lista global items nunca é modificada. Isso normalmente é um bug.
Erro 2: UnboundLocalError ao misturar leitura e escrita
(sem saída)
O Python analisa o corpo inteiro da função antes de executá-la. Se encontrar qualquer atribuição a total, ele marca total como local na função inteira. Aí a linha print(total) tenta ler uma variável local que ainda não recebeu valor.
Exercícios práticos
Hora de pôr escopo e closures em prática. Estes exercícios vão da leitura de escopos até a criação das suas próprias closures.
Leia este código com atenção e preveja o que ele exibe. Depois escreva instruções print() que exibam exatamente esse resultado.
x = 10
def foo():
x = 20
print(x)
foo()
print(x)Exiba os dois números em linhas separadas, exatamente como o código acima os exibiria.
Este código tenta incrementar um contador global, mas quebra com um UnboundLocalError. Corrija-o para que a saída seja:
1
2
3Escreva uma função chamada make_greeter que recebe uma string greeting como argumento e retorna uma nova função. A função retornada deve receber um argumento name e exibir a saudação com o nome.
Por exemplo:
hello = make_greeter("Hello")
hello("World")Deve exibir: Hello, World!
O teste vai chamar:
hi = make_greeter("Hi")
hi("Alice")
hi("Bob")Saída esperada:
Hi, Alice!
Hi, Bob!Escreva uma função chamada make_counter que recebe um valor start e retorna uma função. Cada vez que a função retornada for chamada, ela deve exibir a contagem atual e depois aumentá-la em 1.
O teste vai chamar:
c = make_counter(1)
c()
c()
c()Saída esperada:
1
2
3Escreva uma função chamada make_averager que retorna uma função. Cada vez que a função retornada for chamada com um número, ela deve exibir a média acumulada de todos os números recebidos até agora.
A média acumulada é a soma de todos os números dividida pela quantidade de números.
O teste vai chamar:
avg = make_averager()
avg(10)
avg(20)
avg(30)Saída esperada:
10.0
15.0
20.0Dica: a função precisa lembrar tanto a soma total quanto a quantidade de números.
Resumo
Veja o que você aprendeu sobre escopo em Python:
| Conceito | O que significa |
|---|---|
| Escopo local | Variáveis criadas dentro de uma função; só existem ali |
| Escopo global | Variáveis criadas no nível do módulo; visíveis em qualquer parte desse módulo |
| Regra LEGB | O Python procura em Local, Enclosing, Global e Built-in, nessa ordem |
Palavra-chave global | Permite que uma função modifique uma variável do nível do módulo |
| Escopo envolvente | As variáveis de uma função externa, acessíveis pelas funções internas |
| Closure | Uma função que se lembra de variáveis do seu escopo envolvente |
Palavra-chave nonlocal | Permite que uma função interna modifique uma variável do escopo envolvente |
A regra de ouro: prefira passar argumentos e retornar valores em vez de usar global ou nonlocal. As closures são a única exceção em que o escopo envolvente brilha: elas permitem criar funções especializadas com estado privado.
O que vem a seguir?
Agora que você entende escopo e closures, já pode seguir para Recursão, em que funções chamam a si mesmas. Entender escopo é essencial para a recursão, porque cada chamada recursiva ganha seu próprio escopo local.