Cerrábamos el módulo 3 con una frase: «sabes construir la maquinaria de un programa, y ahora toca aprender a organizarla». Este es ese momento. TareaFácil v0.6 funciona —Marta puede registrar una tarea, consultarla, cambiarle la prioridad y completarla sin relanzar el programa—, pero el fichero tareafacil.py se ha convertido en un muro de casi cien líneas seguidas donde if not hay_tarea: aparece tres veces y cuatro bucles de validación se repiten casi palabra por palabra.

Ese muro no es un problema estético, sino práctico: cuando algo falla, no sabes dónde mirar; cuando quieres cambiar el mensaje de error de la prioridad, tienes que acordarte de cambiarlo en los dos sitios donde está escrito; y cuando quieres añadir una opción al menú, tienes que abrirte paso entre el código de las cinco anteriores.

La herramienta que resuelve todo eso se llama función. Es, probablemente, la idea más rentable de todo el curso: la que convierte un guion inmanejable en un programa que se puede leer, probar y ampliar por partes. En esta lección aprenderás qué es una función, cómo se define, cómo se llama y qué ocurre exactamente cuando la llamas.

Contenido

  1. El problema: código repetido y sin nombre
  2. Qué es una función
  3. Sintaxis: def, cuerpo indentado y llamada
  4. Definir y llamar no son lo mismo
  5. Orden de definición y ejecución: el NameError
  6. El flujo de una llamada, paso a paso
  7. Procedimientos y funciones que calculan: None
  8. Docstring y convenciones de nombre
  9. TareaFácil: las tres primeras funciones
  10. Errores comunes y consejos
  11. Ejercicios
  12. Conclusión

  1. El problema: código repetido y sin nombre

Mira estos dos fragmentos, extraídos tal cual de TareaFácil v0.6. Uno está en la opción 1 y el otro en la opción 3:

# En la opcion 1 (registrar tarea)
prioridad = input("Prioridad     : ").strip().lower()
while prioridad not in PRIORIDADES:
    prioridad = input("No valida. Prioridad: ").strip().lower()

# En la opcion 3 (cambiar prioridad)
nueva = input("Nueva prioridad (alta/media/baja): ").strip().lower()
while nueva not in PRIORIDADES:
    nueva = input("No valida. Prioridad: ").strip().lower()

Son el mismo algoritmo escrito dos veces, y eso trae tres consecuencias desagradables:

  • Mantenimiento doble. Si mañana el estudio añade la prioridad critica, hay que tocar los dos sitios. Y el día que solo toques uno, tendrás un programa que se comporta de forma distinta según por dónde entres: un error especialmente difícil de encontrar.
  • Ruido. Al leer la opción 1 no ves «se pide la prioridad»: ves tres líneas de mecánica con input, strip, lower y un while. El qué queda enterrado bajo el cómo.
  • Imposibilidad de probar por partes. No puedes comprobar «¿funciona bien mi lectura de prioridades?» sin ejecutar la aplicación entera, navegar hasta la opción 1 y teclear a mano.

Lo que falta es poder dar nombre a un trozo de código y usar ese nombre cuando lo necesites. Eso es una función.

  1. Qué es una función

Una función es un bloque de código con nombre que se escribe una vez y se ejecuta tantas veces como haga falta, simplemente mencionando su nombre.

Ya has usado funciones desde la primera lección de Python: print(), input(), len(), int() y type() son funciones integradas (built-in). Piensa en lo que te dan: no tienes ni idea de cómo print consigue que unas letras aparezcan en la pantalla —hay ahí dentro un trabajo considerable con el sistema operativo—, y sin embargo la usas sin esfuerzo. Es la abstracción de la lección 01-02, aplicada ahora a tu propio código. Una función bien hecha te da cuatro cosas:

Beneficio Qué significa en la práctica
Reutilización Escribes el algoritmo una vez y lo invocas desde diez sitios
Nombre para una idea pedir_prioridad() se lee de golpe; tres líneas de while hay que descifrarlas
Verificación por partes Puedes probar una función sola, sin arrancar toda la aplicación
Cambio localizado Corriges el algoritmo en un único lugar y se corrige en todos

De los cuatro, el segundo es el que más subestiman los principiantes. Una función no es solo un ahorro de tecleo: es una palabra nueva en el vocabulario de tu programa. Cuando escribes mostrar_menu(), dejas de pensar en print y empiezas a pensar en menús. Programar bien consiste, en gran medida, en construirse un vocabulario a la altura del problema.

  1. Sintaxis: def, cuerpo indentado y llamada

Definir una función en Python son dos partes: la cabecera y el cuerpo.

def saludar_al_equipo():
    print("Buenos dias, equipo de Estudio Alba.")
    print("Hoy revisamos las tareas pendientes.")

Desmenucemos la cabecera, porque cada símbolo tiene su papel:

  • def es la palabra clave que anuncia «voy a definir una función». Viene del inglés define.
  • saludar_al_equipo es el nombre. Se elige igual que un nombre de variable: snake_case, sin tildes ni espacios, descriptivo.
  • () son los paréntesis, obligatorios aunque estén vacíos. Dentro irán los parámetros, tema de la próxima lección; hoy siempre irán vacíos.
  • : los dos puntos cierran la cabecera y abren el bloque, igual que en un if o un while.

El cuerpo es todo lo indentado debajo (cuatro espacios, como siempre) y termina donde termina la indentación: la primera línea que vuelva al margen izquierdo ya está fuera de la función. Una vez definida, la función se llama escribiendo su nombre seguido de paréntesis. Si añades esto debajo del bloque anterior:

saludar_al_equipo()
saludar_al_equipo()
Buenos dias, equipo de Estudio Alba.
Hoy revisamos las tareas pendientes.
Buenos dias, equipo de Estudio Alba.
Hoy revisamos las tareas pendientes.

Dos líneas de llamada, dos ejecuciones del cuerpo. Y fíjate en que la definición no imprime nada por sí sola: sin esas dos llamadas, el programa se ejecuta sin errores y sin salida ninguna.

  1. Definir y llamar no son lo mismo

Esta distinción es la que más se atraganta al principio. Definir es escribir la receta: Python lee la cabecera, guarda el cuerpo en memoria bajo ese nombre y sigue adelante, sin ejecutar nada. Llamar es cocinar la receta: Python va a buscar el cuerpo guardado y lo ejecuta ahora.

Definición Llamada
Sintaxis def pausar(): + cuerpo pausar()
Cuántas veces se escribe Una Las que hagan falta
¿Ejecuta el cuerpo? No
Cuándo ocurre Al leer el fichero En el punto exacto de la llamada

El error clásico es olvidar los paréntesis al llamar:

def pausar():
    input("Pulsa Intro para continuar...")

pausar     # <-- sin parentesis: NO llama a nada
pausar()   # <-- con parentesis: si la llama

La línea pausar sin paréntesis es válida en Python y no da error. Simplemente evalúa el nombre y obtiene el objeto función, igual que escribir x a secas evalúa la variable x; el resultado se descarta y el programa continúa. En el REPL se ve con claridad:

>>> pausar
<function pausar at 0x7f3c9a1b2e50>
>>> type(pausar)
<class 'function'>

Ahí está: sin paréntesis, pausar es un valor de tipo function; con paréntesis, es una orden de ejecución. Esto parece una trampa hoy, pero es una de las propiedades más potentes de Python y en Funciones como valores la aprovecharemos a fondo. Síntoma típico en un programa real: lo ejecutas, no falla, y sencillamente no pasa nada donde esperabas ver algo. Mira los paréntesis lo primero.

  1. Orden de definición y ejecución: el NameError

Python ejecuta el fichero de arriba abajo. Cuando llega a un def, registra el nombre; cuando llega a una llamada, busca ese nombre. Por tanto: una función debe estar definida antes de la línea que la llama.

mostrar_cabecera()          # ERROR: todavia no existe

def mostrar_cabecera():
    print("TareaFacil - Estudio Alba")
NameError: name 'mostrar_cabecera' is not defined

Un NameError significa exactamente eso: has usado un nombre que Python no tiene registrado. Las tres causas habituales son que lo hayas escrito mal (mostar_cabecera), que lo uses antes de definirlo o —lo veremos en Ámbito de variables— que exista pero no sea visible desde donde estás.

Matiz importante: lo que debe ir antes es la llamada, no otra definición. Una función puede mencionar en su cuerpo a otra que se define más abajo, porque el cuerpo no se ejecuta hasta que se llama:

def preparar_pantalla():
    mostrar_cabecera()      # se resuelve al EJECUTAR, no al definir

def mostrar_cabecera():
    print("TareaFacil - Estudio Alba")

preparar_pantalla()         # aqui ya existen las dos: funciona

Por eso la estructura habitual de un fichero Python es: constantes arriba, después todas las definiciones, y el código que las pone en marcha al final. En Descomponer un programa en funciones formalizaremos esa estructura.

  1. El flujo de una llamada, paso a paso

Cuando Python encuentra una llamada ocurren cinco cosas en orden: se detiene la ejecución en esa línea; se anota el punto de retorno, el sitio exacto al que habrá que volver; se salta al cuerpo y se ejecuta entero, de arriba abajo; al acabar se regresa al punto anotado; y se continúa con la línea siguiente a la llamada. Ese registro de «dónde estábamos» se llama pila de llamadas (call stack): cada llamada apila un marco y cada retorno lo desapila.

flowchart TD
    A["Principal: linea 1"] --> B["Llamada a tomar_nota()"]
    B --> C["Cuerpo de tomar_nota"]
    C --> D["Fin del cuerpo: regreso"]
    D --> E["Principal: linea siguiente"]

Compruébalo con un ejemplo trazable:

def tomar_nota():
    print("2. Entro en la funcion")
    print("3. Salgo de la funcion")

print("1. Antes de la llamada")
tomar_nota()
print("4. Despues de la llamada")
1. Antes de la llamada
2. Entro en la funcion
3. Salgo de la funcion
4. Despues de la llamada

Los números salen en orden porque el programa principal espera: no ejecuta la última línea hasta que la función ha terminado del todo. Es la «prueba de escritorio» de 01-05, ahora con saltos. Y si una función llama a otra, la pila crece: el principal espera a la primera, que a su vez espera a la segunda. Esa capacidad de anidar llamadas sostiene programas de cualquier tamaño, y llevada al extremo —una función que se llama a sí misma— da lugar a la recursión.

  1. Procedimientos y funciones que calculan: None

Hay dos maneras de que una función sea útil. Las funciones que hacen —los clásicos procedimientos— valen por el efecto que producen: imprimir en pantalla, pedir un dato, esperar una tecla. Las funciones que calculan valen por el resultado que entregan a quien las llamó, para que este decida qué hacer con él. Hoy escribiremos solo del primer tipo; el return que hace posible el segundo llega en la lección siguiente. Pero hay un detalle que debes ver ya:

def mostrar_cabecera():
    print("TareaFacil - Estudio Alba")

resultado = mostrar_cabecera()
print(resultado, type(resultado))
TareaFacil - Estudio Alba
None <class 'NoneType'>

Léelo con atención. La función imprimió su texto, pero lo que devolvió fue None. En Python, toda función que termina sin devolver nada explícitamente devuelve None, el valor «ausencia de valor» que conociste en 02-01. No hay funciones que no devuelvan nada: hay funciones que devuelven None. De ahí sale un error muy frecuente:

texto = print("Hola")     # texto vale None, no "Hola"

print muestra el texto; no lo devuelve, así que guardar su resultado no sirve para nada. Imprimir y devolver son operaciones distintas, y confundirlas es de las cosas que más cuesta desaprender.

  1. Docstring y convenciones de nombre

Si la primera línea del cuerpo de una función es una cadena suelta, Python la trata de forma especial: la guarda como documentación. Se llama docstring y se escribe con comillas triples.

def mostrar_cabecera():
    """Pinta el titulo de la aplicacion en la pantalla."""
    print("=" * 46)
    print(f"{'TareaFacil - Estudio Alba':^46}")
    print("=" * 46)

Con eso ganas dos cosas: el editor te muestra esa frase al escribir la llamada, y la función queda autoexplicada para quien la lea dentro de seis meses (que casi siempre eres tú). Desde el REPL, help(mostrar_cabecera) imprime la cabecera seguida de su docstring. Por ahora, una regla mínima: una sola línea, empezando por un verbo, que diga qué hace la función, no cómo lo hace. El formato completo es el tema de Documentación y comentarios. Y ojo con la diferencia frente al comentario # de 02-01: el comentario explica una línea concreta a quien lee el código; el docstring describe la función entera a quien va a usarla sin mirar dentro.

El nombre, por su parte, es la mitad del valor de una función. Las convenciones de la comunidad Python son estas:

Regla Bien Mal
snake_case, minúsculas y guiones bajos mostrar_menu MostrarMenu, mostrarMenu
Empieza por un verbo en infinitivo pedir_prioridad prioridad, datos
Describe qué hace, no cómo pausar hacer_input_vacio
Sin tildes ni caracteres raros anadir_dias añadir_días
Una responsabilidad, sin «y» en el nombre validar_titulo validar_y_guardar_titulo

La razón del verbo es que una función es una acción: las variables guardan cosas (sustantivos), las funciones hacen cosas (verbos). Al leer pausar() entiendes al instante que ahí ocurre algo; si leyeras pausa, dudarías entre una variable y una llamada. Dos prefijos que verás por todas partes: mostrar_* para lo que escribe en pantalla y pedir_* para lo que consulta al usuario. Adoptarlos hace tu código previsible, que es mayor elogio de lo que parece.

  1. TareaFácil: las tres primeras funciones

De momento solo podemos extraer los trozos que no necesitan datos de entrada ni devuelven nada, porque los parámetros son la lección siguiente. Aun así hay tres candidatos evidentes: la cabecera, el menú y la pausa.

# tareafacil.py - Estudio Alba
# Version 0.6.1: primeras funciones sin parametros

ANCHO = 46
PRIORIDADES = ("alta", "media", "baja")
EQUIPO = ("Marta", "Luis", "Nuria")
OPCIONES = ("1", "2", "3", "4", "5")


def mostrar_cabecera():
    """Pinta el titulo enmarcado de la aplicacion."""
    print("\n" + "=" * ANCHO)
    print(f"{'TareaFacil - Estudio Alba':^{ANCHO}}")
    print("=" * ANCHO)


def mostrar_menu():
    """Muestra la cabecera y las cinco opciones disponibles."""
    mostrar_cabecera()
    print(" 1. Registrar / reemplazar la tarea")
    print(" 2. Ver la ficha de la tarea")
    print(" 3. Cambiar la prioridad")
    print(" 4. Marcar como completada")
    print(" 5. Salir")
    print("-" * ANCHO)


def pausar():
    """Detiene la ejecucion hasta que el usuario pulsa Intro."""
    input("\nPulsa Intro para volver al menu...")

Y el bucle principal, que ahora se lee de otra manera:

hay_tarea = False
titulo = ""
responsable = ""
prioridad = ""
dias = 0
completada = False

while True:
    mostrar_menu()
    opcion = input("Elige una opcion (1-5): ").strip()
    while opcion not in OPCIONES:
        opcion = input("Opcion no valida. Elige 1-5: ").strip()

    if opcion == "1":
        pass                    # registro de la tarea (aun sin extraer)
    elif opcion == "5":
        confirmar = input("Seguro que quieres salir? (s/n): ").strip().lower()
        if confirmar == "s":
            print("Hasta luego. TareaFacil se cierra.")
            break

    pausar()

Cuatro observaciones sobre lo que acaba de pasar:

  • Doce líneas de print se han convertido en mostrar_menu(). El bucle principal se lee ya como lo que es: mostrar, leer, despachar, pausar. La mecánica de pintar sigue existiendo, pero ha dejado de estorbar.
  • mostrar_menu() llama a mostrar_cabecera(). Una función puede apoyarse en otra, y así se construyen niveles de abstracción.
  • Las funciones leen las constantes ANCHO, OPCIONES aunque no se las pasemos. Funciona porque son globales; en Ámbito de variables verás por qué, y por qué las constantes son la única excepción aceptable a la regla de no depender de globales.
  • pausar() ignora deliberadamente lo que devuelve input(). Su valor está en el efecto: esperar. Es un procedimiento de manual.

Fíjate también en las dos líneas en blanco entre funciones: es la convención de estilo de Python (la guía PEP 8, que veremos en 08-05) y ayuda mucho a distinguir bloques de un vistazo. Lo que todavía no podemos extraer son los bucles de validación repetidos, porque cada uno necesita su mensaje y sus valores válidos, y tiene que devolver el dato leído.

Errores Comunes y Consejos

Llamar sin paréntesis. pausar evalúa el objeto función y lo tira; pausar() lo ejecuta. No hay error ni aviso: simplemente no ocurre nada. Si tu programa «se salta» un paso, mira los paréntesis primero. Y si lo llamas antes de definirlo, NameError: name 'x' is not defined: coloca todas las definiciones arriba y el código que arranca el programa al final del fichero.

Olvidar los dos puntos o la indentación. def mostrar_menu() sin : da SyntaxError; el cuerpo sin indentar da IndentationError, el mismo de 03-01. Una función necesita al menos una línea indentada: si aún no sabes qué poner, escribe pass.

Definir una función dentro del bucle. Es legal, pero se redefine en cada vuelta sin ningún beneficio y despista al leer. Las definiciones van fuera. Y no esperes que una función se ejecute sola: definir no es ejecutar.

Creer que print devuelve el texto. texto = print("Hola") deja texto valiendo None. Mostrar y devolver son cosas distintas.

Consejo: extrae cuando el código se repite dos veces, no tres. La segunda copia ya es la señal; la tercera suele llegar con una diferencia sutil entre copias, que es un error esperando su turno. Y prueba tus funciones en el REPL: pega la definición, llámala y observa. No necesitas ejecutar la aplicación entera para saber si una pieza funciona, y ese es justamente el regalo de las funciones.

Ejercicios

Ejercicio 1: Predecir la salida

Sin ejecutarlo, escribe qué imprime este programa y por qué. Presta atención a los paréntesis.

def anotar():
    print("Anotado")

print("A")
anotar
print("B")
anotar()
resultado = anotar()
print(resultado)

Ejercicio 2: Tres funciones para el estudio

Escribe panel_alba.py con tres funciones sin parámetros, cada una con su docstring de una línea: mostrar_bienvenida(), que imprime un marco de 40 guiones, el texto Panel de Estudio Alba centrado y otro marco; mostrar_equipo(), que imprime los tres nombres del equipo, uno por línea y precedidos de un guion; y despedirse(), que imprime una línea en blanco y el mensaje Panel cerrado.. Después llama a las tres en orden. La constante EQUIPO debe estar declarada arriba y usarse en mostrar_equipo().

Ejercicio 3: Arreglar el fichero roto

Este fichero tiene cuatro fallos relacionados con funciones. Identifícalos y di qué error produce cada uno, o si no produce ninguno.

mostrar_titulo()

def mostrar_titulo()
    print("TareaFacil")

def mostrar_pie():
print("Fin del informe")

mostrar_pie

Soluciones

Solución 1. La salida es A, B, Anotado, Anotado, None, cada uno en su línea. Repasemos: print("A") imprime A; la línea anotar sin paréntesis evalúa el objeto función y descarta el resultado, sin imprimir ni fallar; print("B") imprime B; anotar() ejecuta el cuerpo e imprime Anotado; la última llamada lo ejecuta otra vez (segundo Anotado) y guarda en resultado lo que devuelve, que al no haber return es None.

Solución 2.

# panel_alba.py - Estudio Alba
ANCHO = 40
EQUIPO = ("Marta", "Luis", "Nuria")


def mostrar_bienvenida():
    """Pinta el marco y el titulo del panel."""
    print("-" * ANCHO)
    print(f"{'Panel de Estudio Alba':^{ANCHO}}")
    print("-" * ANCHO)


def mostrar_equipo():
    """Lista los nombres del equipo, uno por linea."""
    for persona in EQUIPO:
        print(f" - {persona}")


def despedirse():
    """Cierra el panel con un mensaje de despedida."""
    print()
    print("Panel cerrado.")


mostrar_bienvenida()
mostrar_equipo()
despedirse()

El for persona in EQUIPO: es el recorrido de 03-02: dentro de una función funciona exactamente igual que fuera. Y observa el orden: las tres definiciones arriba, las tres llamadas al final. Si movieras mostrar_bienvenida() a la primera línea del fichero, tendrías un NameError.

Solución 3.

Fallo Consecuencia
mostrar_titulo() se llama en la línea 1, antes de definirse NameError al ejecutar
def mostrar_titulo() sin los dos puntos SyntaxError
El cuerpo de mostrar_pie no está indentado IndentationError
mostrar_pie al final, sin paréntesis Ningún error: simplemente no se ejecuta

Los dos errores de sintaxis se detectan antes de ejecutar nada, así que el programa ni siquiera llega a producir el NameError. La versión corregida añade los dos puntos, indenta el print de mostrar_pie, y baja mostrar_titulo() al final del fichero junto a mostrar_pie(), esta vez con paréntesis. El cuarto fallo es el más peligroso precisamente porque no falla: un programa que calla y no hace nada es más difícil de diagnosticar que uno que se rompe con un mensaje claro.

Conclusión

Ya sabes fabricar tus propias herramientas. Una función se define con def nombre(): y un cuerpo indentado, se llama escribiendo nombre() —con paréntesis, siempre—, y debe estar definida antes de la línea que la invoca o Python responderá con un NameError. Al llamarla, el programa se detiene, anota dónde estaba, ejecuta el cuerpo entero y vuelve exactamente al punto siguiente: esa es la pila de llamadas. Las funciones que solo producen un efecto se llaman procedimientos y devuelven None de forma implícita, porque en Python toda función devuelve algo. Y un docstring de una línea, empezando por un verbo, deja la función explicada para quien la use.

Con eso, TareaFácil ha adelgazado un poco: mostrar_cabecera(), mostrar_menu() y pausar() han sacado del bucle principal la mecánica de pintar y esperar. Pero el problema gordo sigue intacto: los bucles de validación repetidos. Y no podemos extraerlos todavía porque a una función así hay que decirle qué mensaje mostrar y qué valores acepta, y esa función tiene que entregarnos el dato ya validado. Decirle datos a una función son los parámetros; que nos entregue un resultado es el return. Esas dos piezas, que son las que convierten una función en algo verdaderamente reutilizable, son el tema de la próxima lección: Parámetros y retorno de valores.

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