Pregunta 1¿Qué imprime este código?stock = 100
def f():
stock = 50
f()
print(stock)
Funciones internas y clausuras — Dominar el ámbito con global y nonlocal
Aprende el ámbito de Python, las funciones internas y las clausuras, y domina las palabras clave global y nonlocal.
En el artículo anterior viste cómo se comportan los argumentos y los valores de retorno. Esta vez profundizarás en algunos temas más relacionados con las funciones: cómo Python mantiene separadas las variables dentro y fuera de una función (ámbito), cómo definir funciones dentro de funciones (funciones internas), y cómo devolver una función que recuerda valores externos (una clausura). En el camino verás cuándo usar global y nonlocal para reescribir una variable que envuelve a la función desde dentro.
Variables globales y locales — Los ámbitos están separados
Las variables definidas fuera de cualquier función son variables globales; las definidas dentro de una función son variables locales. Puedes leer una global desde dentro de una función, pero si asignas al mismo nombre con = valor dentro de la función, Python crea una variable local nueva — algo distinto de la externa.
Si compruebas la dirección de memoria de una variable con id(), verás que la externa y la interna apuntan a ubicaciones diferentes.
stock = 100 # variable global
def show_stock():
print(f"dentro: {stock}") # 100 — lee el valor externo
def try_change():
stock = 50 # crea una variable local nueva
print(f"dentro: {stock}") # 50
show_stock() # dentro: 100
try_change() # dentro: 50
print(f"fuera: {stock}") # fuera: 100 ← la externa no cambia
- stock = 100 — variable global
- Las funciones solo pueden leerla desde dentro
print(stock)→ lee la 100 externa- No crea ninguna variable local
stock = 50— crea una nueva local dentro de la función- No afecta al
stockexterno
Reescribir una variable externa con la palabra clave global
Cuando realmente quieres reescribir una global externa desde dentro de una función, declara global nombre_variable al principio de la función. Eso le dice a Python: «esta es esa global externa, no una local nueva». Sin esa declaración, una asignación como count += 1 mezcla una lectura y una escritura del mismo nombre y obtienes UnboundLocalError («local variable referenced before assignment»).
visit_count = 0
# × Sin global → UnboundLocalError
# def increment():
# visit_count += 1
# increment() ← UnboundLocalError
# ○ global te permite reescribir la global externa
def increment():
global visit_count
visit_count += 1
increment()
increment()
increment()
print(visit_count) # 3
Reduce el uso de global al mínimo
global permite que una función reescriba estado externo de forma silenciosa, lo que hace que una mala escritura aislada a 100 líneas de distancia sea sorprendentemente difícil de rastrear a gran escala.
Por defecto, devuelve un valor con return y asígnalo del lado del que llama, y recurre a las clases (que veremos más adelante) cuando realmente necesites comportamiento con estado.
Cómo viven funciones y variables en memoria — Nombres frente a cosas
Internamente, Python mantiene un espacio de nombres — una búsqueda «nombre → cosa». x = 5 dice «crea el entero 5 en memoria y haz que el nombre x apunte a él».
def f(): ... funciona igual: construye un objeto función (el cuerpo de la función) en memoria y hace que el nombre f apunte a él. Hay exactamente una de estas búsquedas por programa — el espacio de nombres global — que se configura cuando se carga el módulo y se mantiene hasta que el programa termina.
- x = 5 — el nombre
xapunta al entero 5 - def f — el nombre
fapunta a un objeto función - Sigue vivo hasta que el programa termina
- Contiene argumentos y locales
- Se descarta entero al return
- Completamente separado del primero
- Las locales son independientes
def una vez y la función queda registrada en el espacio de nombres global, donde permanece hasta que el programa termina. Cada llamada, sin embargo, construye su propio marco local y lo descarta al retornar. Las funciones internas, las clausuras y nonlocal que vienen a continuación se apoyan en esta estructura de marcos.Funciones internas — Definir funciones dentro de funciones
Apila otro def dentro de una función y obtienes una función interna — una que solo existe para usarse dentro de la función externa. Son una buena forma de dar nombre a un bloque de lógica significativo dentro de una función larga, de modo que el cuerpo se lea como una secuencia limpia de pasos.
Como una función interna no es accesible desde fuera, también encaja bien para ocultar ayudantes que no quieres exponer.
- Llamar a
validatedesde aquí fuera → NameError - No existe fuera
- Contiene los argumentos
name/age - Puede llamar a
validatedesde dentro
- Lee los
name/ageexternos - No expuesta al exterior
def process_user(name, age):
def validate():
if not name or not isinstance(age, int) or age < 0:
raise ValueError("Entrada inválida")
validate() # llama a la función interna
print(f"Procesado: {name} ({age})")
process_user("Ana", 25)
# Procesado: Ana (25)
# validate no es accesible fuera de process_user
# validate() ← NameError
Excelente cuando quieres dividir una función larga
Una vez que una función crece más allá de las 50 o 100 líneas, dividirla en funciones internas con nombre por cada bloque significativo — process_name() / process_age() y demás — convierte la función externa en una lista legible de pasos. Si más adelante quieres reutilizar alguna fuera, promover una función interna a función normal es trivial.
Clausuras — Funciones que recuerdan valores externos
Las funciones internas pueden leer los argumentos y locales de la función externa. Da un paso más — haz que la función externa retorne (return) la función interna — y has construido una función que sigue funcionando con los valores externos integrados. Eso es una clausura.
Es una forma limpia de producir en serie funciones similares que se diferencian solo por un parámetro, como «una función que triplica» y «una función que quintuplica». Toma el parámetro (factor) como argumento externo y refiérete a él desde la función interna: make_multiplier(3) retorna «un multiplicador que recuerda 3», y make_multiplier(5) retorna «un multiplicador que recuerda 5».
def make_multiplier(factor):
def multiply(x):
return x * factor # referencia el factor externo
return multiply
times3 = make_multiplier(3) # función que recuerda factor=3
times5 = make_multiplier(5) # una función separada que recuerda factor=5
print(times3(10)) # 30
print(times5(10)) # 50
print(times3(7)) # 21
Las clausuras entregan funciones «preconfiguradas»
Cuando quieres crear muchos cálculos similares que se diferencian solo por un parámetro — «10% de impuesto» frente a «8% de impuesto» y demás — las clausuras brillan. En vez de pasar el parámetro en cada llamada, entregas una única función preconfigurada, y el código que la llama queda mucho más limpio.
nonlocal — Reescribir la variable de la función envolvente
Las clausuras pueden leer la variable externa sin problema, pero igual que con global, intentar reescribirla vía count += 1 revienta con UnboundLocalError. La palabra clave que permite esa reescritura es nonlocal. Mientras global apunta al nivel del módulo, nonlocal apunta a la local de la función envolvente inmediata.
Encierra el estado dentro de un objeto función
nonlocal es la forma canónica de envolver un contador que crece en cada llamada dentro de una función.
A diferencia de global, el estado queda sellado dentro de un objeto función concreto, así que los efectos secundarios no se propagan, y obtienes un estado más seguro que recurriendo a global.
def create_counter():
x = 0
def increment():
nonlocal x # declara que actualizamos la x de create_counter
x += 1
return x
return increment
counter = create_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
# Un segundo contador tiene su propia x independiente
counter2 = create_counter()
print(counter2()) # 1 — sin relación con counter
counter = create_counter()— recibeincrement- El código exterior no puede tocar
xdirectamente
- x = 0 — contador, creado exactamente una vez
- Lo que
incrementreferencia mediantenonlocal
nonlocal x— apunta a laxexternax += 1actualiza la x externa
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2¿Por qué este código falla? Elige la mejor explicación.count = 0
def inc():
count += 1
inc()
Pregunta 3¿Qué imprime print(times3(10))?def make_multiplier(factor):
def multiply(x):
return x * factor
return multiply
times3 = make_multiplier(3)