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.
- 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
- ¿Qué es una función?
- ¿Para qué sirve?
- ¿Cómo funciona por dentro?
- Sintaxis: def, parámetros y return
- Ejemplo básico: precio con IGV
- Ejemplo intermedio: promedio con umbral
- Caso práctico: tres funciones que colaboran
- Visualización: la pila de llamadas
- Errores comunes
- Buenas prácticas
- Ejercicios
- Soluciones explicadas
- Resumen
- 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,calcularymostrar. - 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.
| Momento | Qué hace Python | Qué ves |
|---|---|---|
def f(...) | Crea un objeto función y lo guarda en el nombre | Nada en pantalla |
f(3) | Crea un marco nuevo y enlaza los parámetros | Nada hasta un print |
return x | Cierra el marco y devuelve x a quien llamó | Nada en pantalla por sí solo |
print(f(3)) | Imprime el valor devuelto | El 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.
# 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 deldefson obligatorios; sin ellos haySyntaxError. - El cuerpo va indentado con 4 espacios, igual que dentro de un
ifo unfor. - La docstring es la cadena de la primera línea del cuerpo: explica qué hace la función y se consulta con
help(). returnsin expresión devuelveNoney 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.
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.
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.
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.
def area(base, altura): resultado = base * altura return resultadototal = area(4, 3)print(total)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.—
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íntoma | Por qué ocurre | Cómo se arregla |
|---|---|---|
TypeError: area() missing 1 required positional argument: 'altura' | Llamaste con menos argumentos de los declarados | Pasa todos los obligatorios o dale un valor por defecto |
TypeError: area() takes 2 positional arguments but 3 were given | Pasaste un argumento de más | Revisa la firma del def y la llamada |
La función devuelve None | Usaste print en vez de return, o no devolviste nada | Sustituye el print por return y guarda el valor |
NameError: name 'total' is not defined | Intentaste leer una variable local fuera del marco | Devuelve el valor con return y recíbelo fuera |
SyntaxError: non-default argument follows default argument | Un obligatorio va después de uno con valor por defecto | Reordena: primero los obligatorios |
def doble(n):
print(n * 2)
resultado = doble(5)
print(resultado + 1)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.
def promedio(a, b):
return (a + b) / 2
print(promedio(14, 17))#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
Temperatura media
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.
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.
Descuento por tramo
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.
returntermina la función y entrega un valor; sinreturn, el resultado esNone.printmuestra en pantalla;returnentrega 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
¿Qué devuelve una función cuyo cuerpo termina sin ejecutar ningún return?
¿Qué ocurre al ejecutar únicamente def f(): print("hola")?
Con def f(a, b=2), ¿cuál de estas llamadas falla?
¿Ya lo entendiste?
Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.