La lección anterior terminó con tres preguntas sin responder. ¿Por qué pedir_opcion puede leer la constante PRIORIDADES sin que se la pasemos? ¿Qué pasaría si dentro de una función escribiéramos titulo = "otro": cambiaría el titulo del programa principal? ¿Y por qué la variable valor que vive dentro de pedir_texto no existe fuera de ella?

Las tres son la misma pregunta con distinta ropa: dónde vive cada nombre y quién puede verlo. Eso se llama ámbito (scope), y es una de esas ideas que, mientras no se entienden, producen errores desconcertantes: funciones que «no ven» una variable que está claramente escrita más arriba, cambios que se pierden sin dejar rastro, valores que aparecen donde no deberían.

Entender el ámbito no es un lujo teórico: es lo que convierte una función en una caja segura, algo que puedes usar sabiendo que no va a estropear nada de lo que hay fuera. Y es, además, la base de una regla de oro que aplicaremos en la lección siguiente: una función bien hecha depende solo de lo que recibe por parámetros.

Contenido

  1. Ámbito local: nacer y morir con la llamada
  2. Ámbito global
  3. La regla LEGB
  4. Leer una global no es lo mismo que asignarla
  5. La palabra global y por qué se desaconseja
  6. Sombreado de nombres
  7. Cómo se pasan los argumentos en Python
  8. Constantes globales: la excepción aceptada
  9. Funciones anidadas y nonlocal
  10. Ámbito y depuración
  11. TareaFácil: auditoría de las funciones
  12. Errores comunes y consejos
  13. Ejercicios
  14. Conclusión

  1. Ámbito local: nacer y morir con la llamada

Toda variable creada dentro de una función —sea por asignación, sea por ser un parámetro— es local a esa función: existe mientras la llamada se ejecuta y desaparece en cuanto termina.

def calcular_coste(dias, tarifa):
    subtotal = dias * tarifa
    impuestos = subtotal * 0.21
    return subtotal + impuestos

print(calcular_coste(4, 110.0))
print(subtotal)
532.4
NameError: name 'subtotal' is not defined

La primera línea funciona; la segunda no. subtotal e impuestos existieron, hicieron su trabajo y se esfumaron: fuera de la función Python no tiene ningún nombre llamado subtotal, y por eso responde con el NameError que ya conoces de 04-01. Esto, que parece una limitación, es en realidad la mejor propiedad de las funciones. Significa que puedes escribir una función usando los nombres que te dé la gana —i, valor, total— con la garantía de que no vas a pisar nada de lo que hay fuera. Sin ámbito local, cada nombre que usaras dentro de una función sería una mina para el resto del programa, y no podrías escribir ni cincuenta líneas sin colisionar contigo mismo. Funciona también entre funciones distintas: si pedir_titulo y pedir_prioridad usan las dos una variable llamada valor, no se estorban en absoluto, porque son dos variables distintas que casualmente comparten nombre, cada una en su propia caja.

  1. Ámbito global

El ámbito global es el del fichero: todo lo que se define en el margen izquierdo, fuera de cualquier función. Esas variables se llaman globales y son visibles desde cualquier punto del módulo, incluido el interior de las funciones.

NOMBRE_ESTUDIO = "Estudio Alba"                 # global

def mostrar_cabecera():
    print(f"TareaFacil - {NOMBRE_ESTUDIO}")     # lectura de una global: funciona

mostrar_cabecera()                              # TareaFacil - Estudio Alba

Aquí está la respuesta a la primera pregunta de la lección: pedir_opcion podía leer PRIORIDADES porque las constantes son globales y las funciones pueden leer lo global sin pedir permiso.

Ahora bien, que se pueda no significa que convenga: una función que lee variables globales cambiantes deja de ser autónoma, porque su resultado ya no depende solo de lo que recibe sino del estado en que esté el programa en ese momento, lo que la vuelve impredecible al leerla e imposible de probar por separado. Volveremos sobre ello en la sección 8, con la única excepción razonable: las constantes.

  1. La regla LEGB

Cuando Python encuentra un nombre, lo busca en cuatro sitios y en este orden, quedándose con el primero que encuentre. La regla se conoce por sus iniciales en inglés: LEGB.

Capa Qué es Ejemplo
L — Local Dentro de la función que se está ejecutando subtotal, un parámetro
E — Enclosing La función que envuelve a esta, si la hay ver sección 9
G — Global El nivel del fichero NOMBRE_ESTUDIO, PRIORIDADES
B — Built-in Lo que Python trae de serie print, len, int, input
flowchart TD
    A["Local: dentro de la funcion"] --> B["Enclosing: funcion que la envuelve"]
    B --> C["Global: el fichero"]
    C --> D["Built-in: print, len, int, input"]
    D --> E["No esta en ninguna capa: NameError"]

Léelo como una búsqueda que baja escalones: si el nombre está en la capa local, se usa ese y no se mira más abajo; si no, se prueba la envolvente; luego la global; luego las funciones integradas; y si tampoco está ahí, NameError. Una consecuencia inmediata y muy útil: el nombre más cercano gana. Si una función tiene una variable local total y además existe una global total, dentro de la función total se refiere siempre a la local. La global sigue existiendo, intacta, pero queda tapada mientras dure la llamada.

  1. Leer una global no es lo mismo que asignarla

Aquí está el punto que más confusión genera de toda la lección. Acabamos de ver que leer una global desde dentro de una función funciona sin más. Cambiemos una sola cosa: asignar en lugar de leer.

contador = 0

def incrementar():
    contador = contador + 1      # intenta ASIGNAR
    print(contador)

incrementar()
UnboundLocalError: cannot access local variable 'contador' where it is not associated with a value

Parece contradictorio, pero la regla es sencilla y no tiene excepciones: si en algún punto del cuerpo de una función hay una asignación a un nombre, ese nombre es local en toda la función. Python decide eso al compilar la función, antes de ejecutar nada. Así que contador es local dentro de incrementar, y la línea contador = contador + 1 intenta leer una local que todavía no tiene valor. De ahí el UnboundLocalError, que es un NameError especializado.

Y ojo con la variante que no da error, aún más peligrosa:

contador = 0

def poner_a_diez():
    contador = 10        # crea una variable LOCAL nueva; la global no se toca
    print(f"Dentro: {contador}")

poner_a_diez()
print(f"Fuera: {contador}")
Dentro: 10
Fuera: 0

La función parece haber cambiado el contador, e incluso lo imprime cambiado, pero fuera todo sigue igual. Este es el error del que hablábamos: no falla, no avisa, y no hace lo que parece. Si alguna vez te preguntas «he asignado la variable dentro de la función y fuera no cambia», esta es la explicación.

  1. La palabra global y por qué se desaconseja

Python ofrece una manera de forzar que la asignación afecte a la variable global: declararla con global al principio del cuerpo.

contador = 0

def incrementar():
    global contador          # "cuando diga contador, me refiero a la global"
    contador = contador + 1

incrementar()
incrementar()
print(contador)              # 2

Funciona. Y aun así, la recomendación de prácticamente toda la comunidad Python es no usarlo salvo en casos muy contados. La razón se entiende mejor viendo lo que rompe:

total_horas = 0

def registrar_jornada(horas):
    global total_horas
    total_horas = total_horas + horas

def calcular_media(dias_trabajados):
    global total_horas
    total_horas = total_horas / dias_trabajados      # destroza el acumulado
    return total_horas

registrar_jornada(8)
registrar_jornada(6)
print(calcular_media(2))        # 7.0
registrar_jornada(5)
print(total_horas)              # 12.0, un valor que ya no significa nada

calcular_media tenía un nombre inocente —«calcular»— y ha modificado el estado compartido. A partir de ahí, total_horas ya no son horas totales ni una media: es un número sin significado, y el programa seguirá funcionando sin quejarse. Imagina esto en un fichero de mil líneas con quince funciones que escriben la misma global: para saber cuánto vale una variable en un punto tendrías que leerte el programa entero.

Con global Con parámetros y return
¿De qué depende el resultado? Del estado del programa Solo de los argumentos
¿Se puede probar aislada? No Sí
¿Quién puede romperla? Cualquier función Nadie más
¿Se ve en la llamada lo que toca? No Sí

La alternativa siempre es la misma: recibir por parámetro y devolver con return. La versión sana sería def registrar_jornada(total, horas): return total + horas, y que el programa principal guarde el resultado. Una línea más y un problema menos.

  1. Sombreado de nombres

Sombrear (shadowing) es declarar un nombre que tapa otro de una capa más externa. Ocurre continuamente y muchas veces es inofensivo, pero tiene una versión que hace daño de verdad: sombrear un built-in.

list = "Cartel feria del libro"     # tapa la funcion integrada list()
print(list)                          # funciona: imprime el texto
print(list("abc"))                   # TypeError: 'str' object is not callable

Al asignar a list, la capa global se ha quedado con ese nombre y la búsqueda LEGB ya no llega nunca a la capa built-in. El error, además, aparece lejos del punto donde se causó, a veces cientos de líneas después. Estos son los nombres que más se sombrean por accidente:

Nombre integrado Para qué sirve Alternativa segura
list Crear listas (módulo 5) lista_tareas, elementos
input Leer del teclado entrada, texto_leido
str, int, float Convertir tipos texto, numero, importe
type Consultar el tipo tipo_tarea, categoria
sum, max, min, len Cálculos sobre colecciones total, mayor, menor

Sombrear input es especialmente cruel en un programa como TareaFácil: escribes input = input("Titulo: ") una vez y la siguiente llamada a input(...) da TypeError: 'str' object is not callable, porque input ya no es la función sino la cadena que tecleaste. La defensa es simple: si tu editor colorea un nombre como si fuera especial, no lo uses para una variable.

  1. Cómo se pasan los argumentos en Python

Ya sabes que un argumento se asigna al parámetro. Pero ¿qué se asigna exactamente, el valor o «la variable»? En Python, lo que se pasa es la referencia al objeto: el parámetro pasa a apuntar al mismo objeto que el argumento. La consecuencia práctica, con los tipos que conoces —números, cadenas, booleanos, todos inmutables—, es tranquilizadora: reasignar el parámetro dentro no afecta a la variable de fuera.

def alargar_plazo(dias):
    print(f"  Recibo dias = {dias}")
    dias = dias + 5                 # reasigna el nombre LOCAL 'dias'
    print(f"  Dentro ahora dias = {dias}")
    return dias

plazo = 4
resultado = alargar_plazo(plazo)
print(f"Fuera plazo = {plazo}, resultado = {resultado}")
  Recibo dias = 4
  Dentro ahora dias = 9
Fuera plazo = 4, resultado = 9

Traza de lo ocurrido: al llamar, el parámetro dias apunta al mismo objeto 4 que plazo. La línea dias = dias + 5 crea un objeto nuevo, 9, y hace que el nombre local dias apunte a él; plazo sigue apuntando a 4, porque el número 4 no ha cambiado —no puede: los enteros son inmutables. La única forma de que el exterior se entere del nuevo valor es el return.

Lo mismo pasa con las cadenas. Si una función normalizar(texto) hace texto = texto.strip().capitalize() y devuelve el resultado, la variable que pasaste desde fuera sigue con sus espacios y sus minúsculas intactos: es lo que viste en 02-01 sobre la inmutabilidad de las cadenas, ahora en el contexto de una llamada. Los métodos de cadena devuelven una cadena nueva; no modifican la original.

Un adelanto honesto: con objetos mutables —las listas del módulo 5— la historia tiene un capítulo más, porque una función sí puede modificar el contenido del objeto recibido y que el cambio se vea fuera. Lo que acabas de aprender sigue siendo cierto (reasignar el nombre nunca afecta al exterior), pero habrá que distinguir entre reasignar y modificar. Hoy, con números y cadenas, no hay ambigüedad posible.

  1. Constantes globales: la excepción aceptada

Después de tanta advertencia contra las globales, ¿por qué PRIORIDADES y EQUIPO viven en el nivel global y las leemos desde cualquier función sin remordimiento? Porque son constantes, y una constante no tiene los defectos de una global cambiante: no cambia nunca durante la ejecución, así que el resultado de la función sigue siendo predecible; se declara en un solo sitio, arriba del fichero, donde cualquiera la encuentra; se distingue a simple vista por el convenio de MAYÚSCULAS de 02-01; y documenta el dominio del problema, porque PRIORIDADES = ("alta", "media", "baja") dice qué prioridades existen en Estudio Alba, y eso es información del negocio, no estado del programa.

Constante global (ANCHO, EQUIPO) Variable global (titulo, contador)
¿Cambia durante la ejecución? No Sí
¿Quién la modifica? Nadie Cualquier función
¿Complica razonar sobre el código? No Mucho
¿Aceptable leerla desde una función? Sí Evítalo

Un matiz de estilo: aunque leer una constante global sea legítimo, a veces conviene pasarla igualmente por parámetro. Es lo que hicimos con pedir_opcion(mensaje, opciones): pasándole las opciones, la función sirve para prioridades, para responsables y para lo que venga; leyendo PRIORIDADES directamente, solo serviría para prioridades. Regla práctica: si la constante forma parte de lo que la función hace, pásala; si es un detalle de presentación compartido por todo el programa, como ANCHO, léela.

  1. Funciones anidadas y nonlocal

Falta explicar la E de LEGB. En Python puedes definir una función dentro de otra, y la de dentro ve los nombres de la de fuera, que forman su ámbito envolvente (enclosing).

def preparar_informe(cliente):
    encabezado = f"Informe para {cliente}"

    def linea(texto):
        return f"{encabezado} | {texto}"      # lee 'encabezado' del ambito envolvente

    print(linea("Tarea completada"))

preparar_informe("Panaderia Sole")   # Informe para Panaderia Sole | Tarea completada

Y si la función interna necesitara asignar a un nombre de la envolvente, existe la palabra nonlocal, hermana de global pero apuntando una capa más arriba en vez de al fichero entero. No la usaremos en el curso: con lo que sabes hoy, la solución limpia sigue siendo pasar el dato por parámetro y devolverlo. Basta con que reconozcas nonlocal cuando lo veas y sepas que se refiere a la capa envolvente. Las funciones anidadas sí volverán a aparecer, con un propósito muy concreto, en Funciones como valores.

  1. Ámbito y depuración

Hay una razón muy práctica para tomarse el ámbito en serio: el estado global disperso multiplica el tiempo que tardas en encontrar un error. Cuando una función depende solo de sus parámetros, diagnosticar un fallo es un problema cerrado: miras los argumentos, miras el valor devuelto y el error está dentro o no está. Cuando depende de globales que cualquiera puede modificar, la pregunta «¿por qué aquí total_horas vale 12?» no se responde mirando la función: hay que reconstruir toda la historia de la ejecución. Es la diferencia entre revisar una habitación y registrar un edificio.

Por eso, cuando algo no cuadre, la primera pregunta útil es: ¿de qué depende esta función? Si la respuesta es «solo de lo que recibe», ya has acotado el problema. Las técnicas concretas para investigar —trazas, puntos de interrupción, lectura de un traceback— son el tema de Depuración y manejo de errores; lo que el ámbito te da es un programa en el que esas técnicas funcionan rápido.

  1. TareaFácil: auditoría de las funciones

Toca revisar lo escrito en 04-02 con la pregunta de la sección anterior: ¿de qué depende cada función? Además de las cuatro que ya conoces, habíamos esbozado una quinta, mostrar_resumen(), para pintar la línea de estado de la tarea.

# La version defectuosa de mostrar_resumen
titulo = ""
prioridad = ""
dias = 0

def mostrar_resumen():
    """Pinta el resumen de la tarea... leyendo el estado global."""
    print(f"{titulo} | {prioridad} | {dias} dias")
Función Depende de Veredicto
pedir_texto(mensaje, obligatorio=True) Sus parámetros Correcta
pedir_opcion(mensaje, opciones) Sus parámetros Correcta
pedir_entero(mensaje, minimo, maximo) Sus parámetros Correcta
clasificar_urgencia(prioridad, dias) Sus parámetros Correcta
mostrar_resumen() Globales titulo, prioridad, dias Defectuosa

Los cuatro primeros están limpios: puedes copiarlos a otro fichero y funcionan tal cual. mostrar_resumen() no: fuera de TareaFácil no sirve para nada, no se puede probar con datos inventados y, si mañana el programa gestionara dos tareas, no habría manera de decirle cuál pintar. La corrección es mecánica —convertir cada global leída en un parámetro:

def mostrar_resumen(titulo, prioridad, dias):
    """Pinta en una linea el resumen de la tarea indicada."""
    print(f"{titulo} | {prioridad} | {dias} dias | {clasificar_urgencia(prioridad, dias)}")

mostrar_resumen("Cartel feria del libro", "alta", 2)   # ... | 2 dias | CRITICA
mostrar_resumen("Menu Panaderia Sole", "baja", 9)      # ... | 9 dias | Normal

Fíjate en las dos llamadas seguidas con datos distintos: eso, que ahora parece trivial, era imposible con la versión que leía globales. Y observa que mostrar_resumen sí llama a clasificar_urgencia, que es una función global: eso no es un problema de ámbito, porque las funciones, como las constantes, se definen una vez y no cambian. Con esto, tareafacil.py queda en un estado saneado: constantes globales arriba, funciones que dependen solo de sus parámetros, y el estado de la tarea todavía en variables sueltas del programa principal. Ese estado suelto es el último cabo que queda, y lo ataremos en la lección siguiente.

Errores Comunes y Consejos

Usar fuera una variable local. NameError. Lo que nace dentro de una función muere con ella: si necesitas ese valor fuera, devuélvelo con return.

Asignar a una global creyendo que la cambias. contador = 10 dentro de una función crea una local nueva y deja la global intacta, sin dar ningún error. Si el valor de fuera «no se actualiza», es esto.

UnboundLocalError. Aparece cuando lees un nombre que también asignas dentro de la función: la asignación lo ha hecho local en todo el cuerpo. Solución: recíbelo por parámetro y devuélvelo.

Sombrear un built-in. Llamar list, input, str o sum a una variable rompe el programa mucho después y en otro sitio. Añade una palabra: lista_tareas, texto_entrada. Y no abuses de global: funciona, y por eso tienta, pero cada global que escribes es una función que ya no puedes razonar por separado.

Consejo: haz la prueba del recorte. Copia una función a un fichero vacío. Si funciona sin arrastrar nada más, es autónoma; si le faltan nombres, esos nombres deberían ser parámetros. Y pon las constantes arriba y en MAYÚSCULAS: es la señal visual de «esto es global a propósito», y hace que cualquier otra global salte a la vista como sospechosa.

Ejercicios

Ejercicio 1: Predecir tres salidas

Di qué imprime cada bloque, y en su caso qué error se produce y por qué.

# Bloque A
x = 5
def f():
    print(x)
f()

# Bloque B
y = 5
def g():
    y = 99
    print(y)
g()
print(y)

# Bloque C
z = 5
def h():
    print(z)
    z = 99
h()

Ejercicio 2: Convertir globales en parámetros

Reescribe este programa para que ninguna función dependa de variables globales cambiantes, usando parámetros y return. Las constantes en MAYÚSCULAS pueden quedarse donde están.

TARIFA = 110.0
horas_acumuladas = 0

def anadir_horas(horas):
    global horas_acumuladas
    horas_acumuladas = horas_acumuladas + horas

def facturar():
    global horas_acumuladas
    return horas_acumuladas * (TARIFA / 8)

anadir_horas(8)
anadir_horas(4)
print(facturar())

Ejercicio 3: Encontrar el sabotaje

Este programa imprime Hola y luego se rompe. Explica exactamente qué ocurre, en qué línea se causa el daño y en cuál se manifiesta, y arréglalo.

def saludar(texto):
    print(texto)

saludar("Hola")
str = "Cartel feria del libro"
print(str)
saludar(str(2026))

Soluciones

Solución 1.

Bloque Salida Explicación
A 5 La función solo lee la global: la búsqueda LEGB no la encuentra en local, baja a global y la usa
B 99 y luego 5 La asignación crea una local y que tapa la global durante la llamada; la global no se toca
C UnboundLocalError Hay una asignación a z en el cuerpo, así que z es local en toda la función, incluido el print anterior a la asignación

El bloque C es el más instructivo: la línea que falla (print(z)) es anterior a la línea culpable (z = 99). Python decide qué nombres son locales al compilar la función, no al ejecutarla línea a línea.

Solución 2.

TARIFA = 110.0

def anadir_horas(acumuladas, horas):
    """Devuelve el nuevo total de horas tras anadir las indicadas."""
    return acumuladas + horas

def facturar(acumuladas, tarifa=TARIFA):
    """Devuelve el importe correspondiente a las horas acumuladas."""
    return acumuladas * (tarifa / 8)

horas = 0
horas = anadir_horas(horas, 8)
horas = anadir_horas(horas, 4)
print(f"{facturar(horas):.2f} EUR")     # 165.00 EUR

El acumulador sigue existiendo, pero ahora vive en el programa principal y viaja explícitamente por parámetros y valores devueltos: en cada línea se ve quién cambia qué. Además, facturar acepta una tarifa distinta si algún día hace falta, sin tocar la constante.

Solución 3. La línea str = "Cartel feria del libro" sombrea la función integrada str, dejando ese nombre apuntando a una cadena. Nada falla todavía: print(str) imprime el texto sin problema. El daño se manifiesta dos líneas después, en str(2026), que intenta llamar a una cadena como si fuera una función y produce TypeError: 'str' object is not callable. La corrección es renombrar la variable:

titulo = "Cartel feria del libro"
print(titulo)
saludar(str(2026))

La moraleja es la distancia entre la causa y el síntoma: en un fichero largo, esas dos líneas podrían estar separadas por doscientas, y el mensaje de error apuntaría a la inocente.

Conclusión

Ya sabes dónde vive cada nombre. Las variables creadas dentro de una función son locales: nacen con la llamada, mueren al terminar y no existen fuera, lo que garantiza que una función no pise nada del resto del programa. Los nombres se buscan siguiendo la regla LEGB —local, envolvente, global, integrado—, y gana siempre el más cercano. Desde dentro de una función se puede leer una global sin más, pero asignarla crea una variable local nueva; forzar lo contrario con global funciona y casi nunca conviene, porque convierte cada función en algo que solo se entiende leyendo el programa entero. Sombrear nombres integrados como list o input produce errores lejos de donde se causaron. Y al pasar argumentos, Python entrega la referencia al objeto: con números y cadenas, inmutables, reasignar dentro nunca afecta a lo de fuera, así que la única vía de comunicación hacia el exterior es el return.

La regla que resume todo esto cabe en una línea: una función debería depender solo de sus parámetros, y comunicarse con el exterior solo por su valor devuelto. Las constantes en MAYÚSCULAS son la excepción aceptada, porque no cambian. Aplicándola, la auditoría de TareaFácil ha dejado limpias las cuatro funciones de validación y ha corregido mostrar_resumen(), que leía el estado global y ahora recibe lo que necesita. Tienes ya las tres piezas de la ingeniería de funciones: definir y llamar (04-01), parámetros y return (04-02) y ámbito (04-03). Lo que falta es el criterio para usarlas a escala: cómo partir un programa entero de cien líneas en funciones del tamaño adecuado, cómo decidir qué va en cada una y en qué orden escribirlas. Eso es Descomponer un programa en funciones, donde TareaFácil pasará por fin de la v0.6 a la v0.7.

Fundamentos de la Programación

Módulo 1: Introducción a la Programación

Módulo 2: Conceptos Básicos

Módulo 3: Estructuras de Control

Módulo 4: Funciones y Procedimientos

Módulo 5: Estructuras de Datos

Módulo 6: Algoritmos Básicos

Módulo 7: Objetos y Organización del Código

Módulo 8: Buenas Prácticas y Herramientas

Módulo 9: Proyecto Final y Cierre del Curso

© Copyright 2026. Todos los derechos reservados