2.16. Escopo

Quando o Python busca um nome dentro de uma função, ele percorre uma sequência específica de escopos. Entender essa sequência explica por que algumas atribuições ocultam (shadow) nomes externos, por que outras os modificam e por que funções aninhadas conseguem lembrar valores de onde foram definidas.

Caixas aninhadas mostrando um escopo local dentro de um escopo de módulo dentro do escopo de built-ins, com uma seta indicando que a busca de nomes caminha para fora a partir do quadro mais interno.

A busca de nomes começa no escopo local da função e caminha para fora, em direção aos escopos de módulo e de built-ins, até encontrar uma correspondência.

2.16.1. Escopo local e de módulo

Os nomes definidos dentro de uma função são locais a essa função e desaparecem quando a chamada termina:

def f():
    x = 10
    print(x)

f()
print(x)              # NameError: x is not defined

Os nomes definidos no nível superior de um arquivo .py são de nível de módulo (às vezes chamados de globais) e são visíveis em todo o arquivo, inclusive dentro de funções:

CAMERA = "OpenMV"

def banner():
    print("running on", CAMERA)

Ler um nome de nível de módulo de dentro de uma função é automático. Atribuir a esse nome de dentro de uma função não é – a atribuição cria uma nova variável local que oculta a de nível de módulo pelo restante da chamada:

counter = 0

def bump():
    counter = counter + 1     # UnboundLocalError

O counter do lado esquerdo torna counter um nome local em bump, então a leitura do lado direito não tem valor para encontrar.

2.16.1.1. A palavra-chave global

Para de fato reatribuir um nome de nível de módulo de dentro de uma função, declare-o como global primeiro:

counter = 0

def bump():
    global counter
    counter += 1

Recorra a global com moderação. Funções que mutam estado oculto são mais difíceis de raciocinar do que funções que recebem valores como argumentos e retornam novos valores. A solução usual para “preciso compartilhar estado” é passar um objeto (uma lista, um dict, uma instância de classe) como argumento e mutar ele em vez disso.

2.16.2. Lambdas

Uma lambda constrói uma pequena função anônima em uma única expressão:

square = lambda x: x * x
square(7)             # 49

É exatamente equivalente a

def square(x):
    return x * x

O corpo de uma lambda deve ser uma única expressão – sem instruções, sem múltiplas linhas. O uso principal é passar uma função minúscula como argumento para algo que recebe uma função:

pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]

Quando o corpo cresce além de uma expressão, mude para um def de verdade. Nomear uma função com def também lhe dá um nome nos tracebacks, o que uma lambda não tem.

2.16.3. Closures

Uma função definida dentro de outra função pode ler nomes do escopo da função que a envolve. A função interna captura esses nomes e continua funcionando mesmo depois que a chamada externa retornou:

def make_adder(n):
    def add(x):
        return x + n
    return add

add5 = make_adder(5)
add10 = make_adder(10)
print(add5(100), add10(100))

Saída:

105 110

add5 e add10 são duas funções separadas, cada uma lembrando seu próprio n. Uma função que constrói e retorna uma função interna personalizada dessa forma é chamada de closure. Essa é a principal razão pela qual uma linguagem precisa de funções aninhadas, para começar – uma forma de embutir algum estado em um valor de função e então repassar esse valor como um único callable.

A leitura de nomes capturados acontece automaticamente. Reassociar um deles requer uma palavra-chave extra. O exemplo abaixo faz o que parece correto e falha:

def make_counter():
    count = 0
    def tick():
        count = count + 1     # UnboundLocalError
        return count
    return tick

A atribuição a count dentro de tick torna count local a tick, da mesma forma que o tornaria local em uma função de nível superior. A palavra-chave nonlocal diz ao Python “este nome reside na função que a envolve, reassocie-o lá”:

def make_counter():
    count = 0
    def tick():
        nonlocal count
        count += 1
        return count
    return tick

c = make_counter()
print(c(), c(), c())

Saída:

1 2 3

nonlocal está para o escopo da função envolvente assim como global está para o escopo de módulo. Observe que mutar um objeto capturado (chamar some_list.append(...), some_dict[k] = v) não precisa de nonlocal – o nome não está sendo reassociado, apenas o objeto para o qual ele aponta está sendo alterado.