2.16. Âmbito

Quando o Python procura um nome dentro de uma função, percorre uma sequência específica de âmbitos. Compreender essa sequência explica por que algumas atribuições ocultam nomes externos, por que outras os modificam, e por que funções aninhadas podem recordar valores do sítio onde foram definidas.

Nested boxes showing a local scope inside a module scope inside the built-in scope, with an arrow indicating that name lookup walks outward from the innermost frame.

A procura de nomes começa no âmbito local da função e avança para fora até aos âmbitos do módulo e dos embutidos, até encontrar uma correspondência.

2.16.1. Âmbito 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 ao nível superior de um ficheiro .py são de âmbito de módulo (por vezes chamados globais) e são visíveis em todo o ficheiro, incluindo dentro de funções:

CAMERA = "OpenMV"

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

Ler um nome de âmbito de módulo a partir do interior de uma função é automático. Atribuir a esse nome a partir do interior de uma função não o é – a atribuição cria uma nova variável local que oculta a de âmbito de módulo durante o resto da chamada:

counter = 0

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

O counter no lado esquerdo torna counter um nome local em bump, pelo que a leitura no lado direito não encontra nenhum valor.

2.16.1.1. A palavra-chave global

Para reatribuir efetivamente um nome de âmbito de módulo a partir do interior de uma função, declare-o global primeiro:

counter = 0

def bump():
    global counter
    counter += 1

Use global com parcimónia. Funções que alteram estado oculto são mais difíceis de raciocinar do que funções que recebem valores como argumentos e devolvem novos valores. A solução habitual para «preciso de partilhar estado» é passar um objeto (uma lista, um dict, uma instância de classe) como argumento e mutá-lo a ele em vez disso.

2.16.2. Lambdas

Um lambda constrói uma pequena função anónima numa única expressão:

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

É exatamente equivalente a

def square(x):
    return x * x

O corpo de um lambda tem de ser uma única expressão – sem instruções, sem múltiplas linhas. O uso principal é passar uma pequena função como argumento a algo que aceita 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 para além de uma expressão, mude para um def real. Nomear uma função com def também lhe atribui um nome nos rastreios de erros, o que um lambda não tem.

2.16.3. Closures

Uma função definida dentro de outra função pode ler nomes do âmbito da função envolvente. A função interna captura esses nomes e continua a funcionar mesmo depois de a chamada externa ter retornado:

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))

Resultado:

105 110

add5 e add10 são duas funções distintas, cada uma lembrando o seu próprio n. Uma função que constrói e devolve uma função interna personalizada desta forma chama-se closure. É a principal razão pela qual uma linguagem precisa de funções aninhadas – uma forma de incorporar algum estado num valor de função e depois passar esse valor como um único callable.

Ler nomes capturados acontece automaticamente. Reatribuir um requer uma palavra-chave adicional. 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 numa função de nível superior. A palavra-chave nonlocal diz ao Python «este nome vive na função envolvente, reatribui-o aí»:

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

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

Resultado:

1 2 3

nonlocal é para o âmbito da função envolvente o que global é para o âmbito do módulo. Note que mutar um objeto capturado (chamar some_list.append(...), some_dict[k] = v) não precisa de nonlocal – o nome não está a ser reatribuído, apenas o objeto para o qual aponta está a ser alterado.