Aprende leyendo en orden

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
Dentro y fuera son ámbitos separados — Vista en diagrama de conjuntos
Módulo (espacio de nombres global)
  • stock = 100 — variable global
  • Las funciones solo pueden leerla desde dentro
Espacio de nombres local de show_stock()
  • print(stock) → lee la 100 externa
  • No crea ninguna variable local
Espacio de nombres local de try_change()
  • stock = 50 — crea una nueva local dentro de la función
  • No afecta al stock externo
Los espacios de nombres locales se anidan dentro del módulo. Las lecturas alcanzan el exterior, pero las asignaciones quedan selladas como locales separadas.

Usa una variable global stock para confirmar que las lecturas alcanzan el exterior, pero las asignaciones internas no.

① Declara una global stock = 100.

② Define def show_stock(): e imprime f"dentro: {stock}".

③ Define def try_change():, asigna stock = 50 y luego imprime f"dentro: {stock}".

④ Llama a show_stock()try_change()print(f"fuera: {stock}") y comprueba que el valor externo no cambió.

(Cuando la respuesta sea correcta, aparecerá la explicación.)

Editor Python

Ejecutar el código para ver el resultado

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

global declara «esa variable externa»
sin globalcount += 1UnboundLocalErrorglobal countcount += 1reescribe elcount externofallafunciona
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.

Construye un contador de visitas a una página usando global.

① Declara visit_count = 0 fuera de cualquier función.

② Define def increment_visit():. Al principio de la función, escribe global visit_count y luego visit_count += 1.

③ Llama a increment_visit() tres veces y luego imprime f"visitas: {visit_count}".

Editor Python

Ejecutar el código para ver el resultado

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.

Marcos de módulo y de función en memoria
Espacio de nombres global (módulo)
  • x = 5 — el nombre x apunta al entero 5
  • def f — el nombre f apunta a un objeto función
  • Sigue vivo hasta que el programa termina
Marco de la primera llamada a f()
  • Contiene argumentos y locales
  • Se descarta entero al return
Marco de la segunda llamada a f()
  • Completamente separado del primero
  • Las locales son independientes
Cada llamada a una función obtiene un espacio de nombres local nuevo (marco) en memoria, que desaparece al salir. Solo hay un espacio de nombres global para todo el programa.
Cómo def y las llamadas a función usan la memoria
①Ejecuta`def f(): ...`Registra la funciónen el ns globalPermanece en el ns globalhasta que el programa sale②Primera llamada: f()Crea un espacio de nombreslocal nuevo (marco)Descarta el marcoal `return`③Segunda llamada: f()Construye un marco nuevo(independiente del #1)Descarta el marcoal `return`en ejecuciónllamaral salirllamar otra vezal salir
Ejecuta 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.

Ámbito de funciones internas — Vista en diagrama de conjuntos
Módulo (espacio de nombres global)
  • Llamar a validate desde aquí fuera → NameError
  • No existe fuera
Marco de process_user()
  • Contiene los argumentos name / age
  • Puede llamar a validate desde dentro
Marco de validate()
  • Lee los name / age externos
  • No expuesta al exterior
validate solo vive dentro de process_user. Puede leer los argumentos externos, y el mundo exterior nunca la ve.
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 significativoprocess_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.

Reorganiza una función de procesamiento de pedidos con una función interna dedicada a las comprobaciones de entrada.

① Define def process_order(item, quantity):.

② Dentro, define def validate():. Dentro de ella, raise ValueError("Pedido inválido") si item está vacío, quantity no es un int, o quantity es 0 o menor.

③ Llama a validate() y luego imprime f"Pedido recibido: {quantity} x {item}".

④ Llama a process_order("manzana", 3) y comprueba el resultado.

Editor Python

Ejecutar el código para ver el resultado

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
Cómo funciona una clausura — Definición y flujo de llamada
①Llama amake_multiplier(3)②Marco externoconstruido con factor=3④return multiply(recuerda factor=3)③def multiplyrefiere a factor dentro⑤times3 = make_multiplier(3)⑥times3(10)→ 10 × 3(factor) = 30ejecutardefinir dentroretornar la fn que recuerdarecibirllamar
El marco externo de make_multiplier desaparece al retornar, pero el multiply construido dentro sale recordando factor=3.

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.

Construye una función que retorne una función que aplica un descuento y recuerda la tasa.

① Define def make_discounter(rate):. Dentro, define def apply(price): que retorne int(price * (1 - rate)). No olvides return apply.

② Construye dos funciones: discount_10 = make_discounter(0.1) y discount_30 = make_discounter(0.3).

③ Imprime discount_10(1000) y discount_30(1000), y comprueba que se aplican tasas de descuento distintas al mismo precio.

Editor Python

Ejecutar el código para ver el resultado

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
nonlocal actualiza la variable de la función envolvente — Vista en diagrama de conjuntos
Módulo (espacio de nombres global)
  • counter = create_counter() — recibe increment
  • El código exterior no puede tocar x directamente
Marco de create_counter()
  • x = 0 — contador, creado exactamente una vez
  • Lo que increment referencia mediante nonlocal
Marco de increment()
  • nonlocal x — apunta a la x externa
  • x += 1 actualiza la x externa
nonlocal apunta a la variable de la función envolvente inmediata. El estado vive dentro del objeto función, no en el espacio de nombres global.

Construye algo parecido a un generador de IDs que emita IDs de pedido empezando en 1, usando una clausura con nonlocal.

① Define def create_order_id_issuer(): e inicializa next_id = 1 al principio.

② Dentro, define def issue():. Tras nonlocal next_id, escribe current = next_idnext_id += 1return current.

③ Por último, return issue para entregar la función interna.

④ Obtenla con issue_id = create_order_id_issuer() y llama a print(issue_id()) tres veces — confirma que ves 1 → 2 → 3.

Editor Python

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1¿Qué imprime este código?
stock = 100
def f():
stock = 50
f()
print(stock)

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)