AulaML Python · C++
Python principiante · 40 min · Funciones

Funciones: parámetros y retorno

Dar nombre a un cálculo, pasarle datos y devolver un resultado en lugar de imprimirlo

Una función encapsula un cálculo bajo un nombre, recibe parámetros y devuelve un resultado con return. Aprenderás a definirla, a distinguir imprimir de devolver, a usar valores por defecto y a seguir la pila de llamadas cuando algo falla.

Lección 1 de 3 del módulo
Al terminar esta lección podrás
  • Definir funciones con def, parámetros y return
  • Distinguir el efecto de print del valor que entrega return
  • Explicar por qué una función sin return devuelve None
  • Usar valores por defecto y argumentos con nombre
  • Componer varias funciones para resolver un cálculo mayor
  • Leer la pila de llamadas y los errores de argumentos

Antes necesitas: Recorridos y comprensiones

En esta lección
  1. ¿Qué es una función?
  2. ¿Para qué sirve?
  3. ¿Cómo funciona por dentro?
  4. Sintaxis: def, parámetros y return
  5. Ejemplo básico: precio con IGV
  6. Ejemplo intermedio: promedio con umbral
  7. Caso práctico: tres funciones que colaboran
  8. Visualización: la pila de llamadas
  9. Errores comunes
  10. Buenas prácticas
  11. Ejercicios
  12. Soluciones explicadas
  13. Resumen
  14. Evaluación

Ya escribes programas que recorren datos, toman decisiones y acumulan resultados. El problema aparece cuando un cálculo se repite en cinco lugares: si mañana cambia la regla del IGV o el umbral de aprobación, tienes que buscar y corregir los cinco bloques.

Una función resuelve exactamente eso: le pones nombre a un fragmento de lógica, lo escribes una vez y lo usas tantas veces como necesites. En esta lección vas a aprender a definirla, a pasarle información, a recoger su resultado y a leer la pila de llamadas cuando algo se rompe. Es el paso que convierte un script suelto en un programa que se puede entender y reutilizar.

¿Qué es una función?

Una función es un bloque de código con nombre que recibe cero o más datos de entrada, hace un trabajo y puede devolver un resultado. Definirla no ejecuta nada: es como escribir una receta en una tarjeta. Llamarla es cocinar el plato; puedes cocinarlo diez veces con ingredientes distintos y la receta no cambia.

  • Nombre: la etiqueta con la que la llamas después, por ejemplo precio_con_igv.
  • Parámetros: los huecos que declaras entre paréntesis en la definición, como precio.
  • Argumentos: los valores concretos que pasas al llamarla, como 250.
  • Cuerpo: las líneas indentadas que se ejecutan en cada llamada.
  • Return: la instrucción que termina la función y entrega un valor a quien la llamó.

¿Para qué sirve?

Cada vez que un cálculo aparece más de una vez, o que un bloque necesita un comentario para explicarse, hay una función esperando a nacer. Estos son los usos que verás de inmediato:

  • Evitar duplicación: la fórmula del IGV se escribe una vez y se aplica a cien productos.
  • Nombrar la intención: calcular_total(items) se lee mejor que quince líneas de multiplicaciones.
  • Dividir un problema: un informe se convierte en leer_datos, limpiar, calcular y mostrar.
  • Probar por piezas: puedes comprobar una función con valores concretos sin ejecutar todo el programa.
  • Reutilizar: la misma función de normalización sirve en un script, en una API y en un notebook de Machine Learning.

¿Cómo funciona por dentro?

Cuando Python ejecuta un def, no ejecuta el cuerpo: crea un objeto de tipo función y lo asocia al nombre. Puedes comprobarlo con print(precio_con_igv): verás algo como <function precio_con_igv at 0x...>, una dirección de memoria.

Al llegar a precio_con_igv(250), Python busca el objeto, crea un marco de ejecución (en inglés, frame), enlaza cada parámetro con su argumento y ejecuta el cuerpo línea por línea. Cuando encuentra return, calcula la expresión, destruye el marco y entrega el valor al punto exacto desde donde se hizo la llamada. Si el cuerpo termina sin return, el valor entregado es None.

Los marcos se apilan: si resumen llama a subtotal, el marco de resumen queda esperando debajo mientras subtotal trabaja arriba. Esa pila de llamadas es la que verás en la visualización y la que Python imprime, de abajo hacia arriba, cuando ocurre una excepción.

MomentoQué hace PythonQué ves
def f(...)Crea un objeto función y lo guarda en el nombreNada en pantalla
f(3)Crea un marco nuevo y enlaza los parámetrosNada hasta un print
return xCierra el marco y devuelve x a quien llamóNada en pantalla por sí solo
print(f(3))Imprime el valor devueltoEl resultado en la terminal

Sintaxis: def, parámetros y return

La forma canónica es def nombre(parámetros):, seguida de un bloque indentado. El return es opcional, pero si lo omites la función devuelve None. Los parámetros con valor por defecto se escriben después de los obligatorios; si los mezclas al revés, Python lanza un SyntaxError.

PYTHON anatomía.py
# Nombre, parámetro obligatorio y parámetro con valor por defecto
def saludar(nombre, saludo="Hola"):
    """Devuelve un saludo personalizado para nombre."""
    return f"{saludo}, {nombre}"

print(saludar("Ada"))
print(saludar("Grace", saludo="Buenos días"))
print(saludar("Alan", "Bienvenido"))
  • Los dos puntos : al final del def son obligatorios; sin ellos hay SyntaxError.
  • El cuerpo va indentado con 4 espacios, igual que dentro de un if o un for.
  • La docstring es la cadena de la primera línea del cuerpo: explica qué hace la función y se consulta con help().
  • return sin expresión devuelve None y también termina la ejecución en ese punto.
  • Puedes devolver varios valores separados por comas: en realidad devuelves una tupla.
  • Si un parámetro tiene valor por defecto, todos los que vengan después también deben tenerlo.

Ejemplo básico: precio con IGV

Empecemos por el cálculo que aparece en cualquier boleta. La función recibe un precio neto y devuelve el total. Fíjate en que no imprime nada: solo entrega el número.

PYTHON igv.py
def precio_con_igv(precio):
    """Recibe un precio neto y devuelve el total con 18 % de IGV."""
    total = precio * 1.18
    return total

neto = 250
final = precio_con_igv(neto)
print(f"Neto: {neto} | Final: {final:.2f}")

La línea 1 define la función; en ese instante no se calcula nada. La línea 2 es una docstring: texto informativo que Python guarda como documentación. En la línea 3 se crea la variable local total dentro del marco de la llamada, con el resultado de 250 * 1.18, es decir 295.0.

La línea 4 devuelve ese valor y cierra el marco. La asignación de la línea 7 lo recibe en final, y la f-string de la línea 8 lo formatea con dos decimales: 295.00. Si borraras el return, final valdría None y el formato :.2f lanzaría un TypeError.

Ejemplo intermedio: promedio con umbral

Una función puede devolver más de un dato y puede tener un comportamiento especial para entradas vacías. Este ejemplo calcula la media de una lista y también si alcanza la nota mínima, que por defecto es 13.

PYTHON promedio.py
def promedio(notas, aprobado=13):
    """Devuelve la media y si alcanza la nota mínima."""
    if not notas:
        return None
    media = sum(notas) / len(notas)
    return media, media >= aprobado

media, aprobo = promedio([14, 9, 17])
print(f"Media: {media:.1f} | Aprobó: {aprobo}")
print(promedio([]))

La línea 3 es una cláusula de guarda: si la lista está vacía, sale de inmediato devolviendo None. Sin ella, len(notas) valdría cero y la división lanzaría ZeroDivisionError. Es mejor devolver un valor reconocible que provocar una excepción en un caso previsible.

La línea 6 devuelve una tupla con dos elementos. La línea 8 la desempaqueta en dos variables: media recibe 13.333... y aprobo recibe True, porque 13.33 supera el umbral. La última línea muestra que la lista vacía produce None, no un error.

Caso práctico: tres funciones que colaboran

En una tienda, el total de una venta combina cantidad, precio y descuento. En lugar de un bloque largo, separa cada responsabilidad: una función multiplica, otra aplica el porcentaje y una tercera coordina. Así puedes cambiar la regla del descuento sin tocar el cálculo del subtotal.

PYTHON venta.py
def subtotal(precio, cantidad):
    """Importe antes de descuentos."""
    return precio * cantidad


def aplicar_descuento(monto, porcentaje):
    """Resta el porcentaje indicado al monto."""
    return monto * (1 - porcentaje / 100)


def resumen_venta(precio, cantidad, descuento=0):
    """Devuelve el importe base y el total con descuento."""
    base = subtotal(precio, cantidad)
    total = aplicar_descuento(base, descuento)
    return base, total


base, total = resumen_venta(48.5, 3, descuento=10)
print(f"Base: {base:.2f}")
print(f"Total: {total:.2f}")

resumen_venta demuestra la composición: su cuerpo no repite fórmulas, delega en las otras dos. Si mañana el IGV entra en el subtotal, solo cambia subtotal. El resultado es 145.50 de base y 130.95 tras el 10 %, porque 48.5 * 3 = 145.5 y 145.5 * 0.9 = 130.95.

Visualización: la pila de llamadas

Observa paso a paso qué ocurre cuando el programa llama a area(4, 3). La línea resaltada indica dónde está Python en cada momento; el panel de la izquierda muestra los marcos activos y el de variables, el estado del módulo principal.

Ejecución visual ·Qué ocurre al llamar a una función
1 / 6
1 def area(base, altura):
2 resultado = base * altura
3 return resultado
4
5 total = area(4, 3)
6 print(total)
El def crea el objeto función y lo guarda en area. El cuerpo todavía no se ha ejecutado: no existe ningún marco de area.
Variables
area = <function area>
Salida
—

Avanza paso a paso: observa la línea resaltada, las variables y la salida.

En el paso 4 el marco de area aún muestra resultado = 12, pero al volver al módulo desaparece. Por eso print(resultado) fuera de la función lanzaría NameError: esa variable vivía en un marco que ya no existe.

Errores comunes

Mensaje o síntomaPor qué ocurreCómo se arregla
TypeError: area() missing 1 required positional argument: 'altura'Llamaste con menos argumentos de los declaradosPasa todos los obligatorios o dale un valor por defecto
TypeError: area() takes 2 positional arguments but 3 were givenPasaste un argumento de másRevisa la firma del def y la llamada
La función devuelve NoneUsaste print en vez de return, o no devolviste nadaSustituye el print por return y guarda el valor
NameError: name 'total' is not definedIntentaste leer una variable local fuera del marcoDevuelve el valor con return y recíbelo fuera
SyntaxError: non-default argument follows default argumentUn obligatorio va después de uno con valor por defectoReordena: primero los obligatorios
Imprimir no es devolver
Incorrecto
PYTHON Devuelve None
def doble(n):
    print(n * 2)

resultado = doble(5)
print(resultado + 1)
Correcto
PYTHON Correcto
def doble(n):
    return n * 2

resultado = doble(5)
print(resultado + 1)

La primera versión escribe 10 en pantalla, pero la función termina sin `return`, así que `resultado` vale `None` y `None + 1` lanza `TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'`. Con `return`, el valor viaja a la variable y se puede seguir usando.

Regla práctica: usa print solo en la capa que presenta resultados, nunca dentro de una función que calcula. Si una función imprime además de devolver, tendrás salida duplicada y costará reutilizarla.

Buenas prácticas

  • Un nombre de función describe una acción: calcular_total, validar_correo, cargar_datos.
  • Haz una sola cosa por función; si necesitas «y» para describirla, probablemente son dos.
  • Devuelve valores en lugar de imprimirlos: así la función sirve en una web, en un test y en un notebook.
  • Escribe una docstring breve con qué recibe y qué devuelve; cuesta diez segundos y ahorra minutos.
  • Evita más de tres o cuatro parámetros posicionales: usa valores por defecto o agrupa en un diccionario.
  • Usa cláusulas de guarda al principio para los casos inválidos y deja el camino normal sin anidar.
Una función en los dos lenguajes
Python
PYTHON promedio.py
def promedio(a, b):
    return (a + b) / 2

print(promedio(14, 17))
C++
CPP promedio.cpp
#include <iostream>

double promedio(double a, double b) {
    return (a + b) / 2.0;
}

int main() {
    std::cout << promedio(14, 17) << std::endl;
    return 0;
}

En C++ hay que declarar el tipo de cada parámetro y del valor devuelto, y la función debe existir antes de usarse (o declararse antes). En Python los tipos son dinámicos y el `def` puede aparecer en cualquier punto anterior a la llamada. La división también cambia: `14/2` en Python da `7.0`; en C++ con enteros daría `7`.

Ejercicios

Ejercicio 1

Temperatura media

Escribe una función a_fahrenheit(celsius) que devuelva la conversión con la fórmula c * 9 / 5 + 32, y otra promedio(datos) que devuelva la media de una lista no vacía. Después calcula la media de [18.5, 21.0, 19.4] y muestra su equivalente en Fahrenheit con un decimal.
Pista

La segunda función puede usar sum(datos) / len(datos). Llama a promedio primero y pasa su resultado a a_fahrenheit: esa es la composición.

Completar código Completa el área
def area_rectangulo(base, altura):
    """Devuelve el área del rectángulo."""
    return  * 

print(area_rectangulo(4, 3))

Dentro de la función solo existen los nombres declarados en el `def`; los valores concretos llegan en la llamada.

Ejercicio 2

Descuento por tramo

Una tienda aplica 5 % de descuento a partir de 100 y 10 % a partir de 300. Escribe descuento_para(monto) que devuelva el porcentaje, y total_final(monto) que lo aplique y devuelva el importe a pagar. Prueba con 80, 150 y 420, mostrando cada resultado con dos decimales.
Pista

En descuento_para, evalúa primero el tramo más alto. Recuerda que una función termina en el primer return que se ejecuta.

Soluciones explicadas

En el primer ejercicio, la tentación es imprimir dentro de a_fahrenheit. Al devolver, en cambio, el valor se puede guardar, formatear o pasar a otra función. La composición a_fahrenheit(promedio(datos)) funciona porque ambas respetan el mismo contrato: reciben un número y devuelven un número. Ese contrato es lo que hace que las piezas encajen.

En el segundo, la clave es separar la decisión del cálculo. descuento_para responde una pregunta («¿qué porcentaje toca?») y total_final hace la aritmética. Si mañana los tramos cambian, solo se toca la primera. Además, el return 0 final actúa como caso por defecto y evita que la función termine devolviendo None cuando el monto es pequeño.

Resumen

  • def nombre(parámetros): define una función; el cuerpo va indentado con 4 espacios.
  • Definir no ejecuta: la llamada es la que crea un marco y ejecuta el cuerpo.
  • return termina la función y entrega un valor; sin return, el resultado es None.
  • print muestra en pantalla; return entrega a quien llamó. No son equivalentes.
  • Los parámetros con valor por defecto van después de los obligatorios.
  • Una función puede devolver una tupla y el llamador puede desempaquetarla.
  • La pila de llamadas muestra los marcos activos; al volver, el local desaparece.

Evaluación

Evaluación 1

¿Qué devuelve una función cuyo cuerpo termina sin ejecutar ningún return?

Evaluación 2

¿Qué ocurre al ejecutar únicamente def f(): print("hola")?

Evaluación 3

Con def f(a, b=2), ¿cuál de estas llamadas falla?

Tus notas

¿Ya lo entendiste?

Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.