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