2.16. Ámbito¶
Cuando Python busca un nombre dentro de una función, recorre una secuencia específica de ámbitos. Entender esa secuencia explica por qué algunas asignaciones ocultan nombres externos, por qué otras los modifican y por qué las funciones anidadas pueden recordar valores del lugar donde se definieron.
La búsqueda de nombres comienza en el ámbito local de la función y avanza hacia afuera hasta los ámbitos del módulo y de las funciones integradas hasta encontrar una coincidencia.¶
2.16.1. Ámbito local y de módulo¶
Los nombres definidos dentro de una función son locales a esa función y desaparecen cuando la llamada termina:
def f():
x = 10
print(x)
f()
print(x) # NameError: x is not defined
Los nombres definidos en el nivel superior de un archivo .py son de nivel de módulo (a veces llamados globales) y son visibles en todas partes de ese archivo, incluso dentro de las funciones:
CAMERA = "OpenMV"
def banner():
print("running on", CAMERA)
Leer un nombre de nivel de módulo desde dentro de una función es automático. Asignar a ese nombre desde dentro de una función no lo es: la asignación crea una nueva variable local que oculta la de nivel de módulo durante el resto de la llamada:
counter = 0
def bump():
counter = counter + 1 # UnboundLocalError
El counter del lado izquierdo convierte a counter en un nombre local dentro de bump, por lo que la lectura del lado derecho no tiene ningún valor que encontrar.
2.16.1.1. La palabra clave global¶
Para reasignar realmente un nombre de nivel de módulo desde dentro de una función, decláralo primero como global:
counter = 0
def bump():
global counter
counter += 1
Recurre a global con moderación. Las funciones que mutan estado oculto son más difíciles de razonar que las funciones que toman valores como argumentos y devuelven valores nuevos. La solución habitual para «necesito compartir estado» es pasar un objeto (una lista, un diccionario, una instancia de clase) como argumento y mutarlo a él en su lugar.
2.16.2. Lambdas¶
Una lambda construye una pequeña función anónima en una sola expresión:
square = lambda x: x * x
square(7) # 49
Es exactamente equivalente a
def square(x):
return x * x
El cuerpo de una lambda debe ser una sola expresión: ni instrucciones, ni varias líneas. Su uso principal es pasar una función diminuta como argumento a algo que toma una función:
pairs = [("b", 2), ("a", 3), ("c", 1)]
pairs.sort(key=lambda item: item[1])
# [('c', 1), ('b', 2), ('a', 3)]
Cuando el cuerpo crece más allá de una expresión, cambia a un def real. Nombrar una función con def también le da un nombre en los rastreos (tracebacks), que una lambda no tiene.
2.16.3. Clausuras¶
Una función definida dentro de otra función puede leer nombres del ámbito de la función que la contiene. La función interna captura esos nombres y sigue funcionando incluso después de que la llamada externa haya 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))
Salida:
105 110
add5 y add10 son dos funciones separadas, cada una recordando su propio n. A una función que construye y devuelve de esta manera una función interna personalizada se le llama clausura. Es la principal razón por la que un lenguaje necesita funciones anidadas en primer lugar: una forma de incorporar cierto estado en un valor de función y luego entregar ese valor como un único objeto invocable.
Leer nombres capturados ocurre automáticamente. Reasignar uno necesita una palabra clave adicional. El ejemplo de abajo hace lo que parece correcto y falla:
def make_counter():
count = 0
def tick():
count = count + 1 # UnboundLocalError
return count
return tick
La asignación a count dentro de tick convierte a count en local de tick, igual que lo habría hecho local en una función de nivel superior. La palabra clave nonlocal le indica a Python «este nombre vive en la función que la contiene, reasígnalo allí»:
def make_counter():
count = 0
def tick():
nonlocal count
count += 1
return count
return tick
c = make_counter()
print(c(), c(), c())
Salida:
1 2 3
nonlocal es para el ámbito de la función contenedora lo que global es para el ámbito del módulo. Ten en cuenta que mutar un objeto capturado (llamar a some_list.append(...), some_dict[k] = v) no necesita nonlocal: el nombre no se reasigna, solo se cambia el objeto al que apunta.