Código
import math
def area_circulo(radio):
return math.pi * radio ** 2
print(area_circulo(2))12.566370614359172
Código
print(area_circulo(5))78.53981633974483
Código
print(area_circulo(7.5))176.71458676442586
Fecha: 17 de noviembre, 2026
Desde el inicio del curso hemos usado funciones: print(), len(), type(), sum(), max()… Son bloques de código con un nombre que realizan una tarea. En este capítulo aprenderemos a crear nuestras propias funciones y a escribirlas siguiendo buenas prácticas.
En este capítulo veremos:
def y return.return y print().lambda y recursión.Imagina que necesitas calcular el área de un círculo en varias partes de tu programa:
area_1 = 3.141592653589793 * 2 ** 2
area_2 = 3.141592653589793 * 5 ** 2
area_3 = 3.141592653589793 * 7.5 ** 2Si descubres un error en la fórmula, tendrás que corregirlo en cada línea. Con una función, la fórmula se escribe una sola vez:
import math
def area_circulo(radio):
return math.pi * radio ** 2
print(area_circulo(2))12.566370614359172
print(area_circulo(5))78.53981633974483
print(area_circulo(7.5))176.71458676442586
| Ventaja | ¿Qué significa? |
|---|---|
| Reutilizar código | Escribes la lógica una vez y la usas muchas veces. |
| No repetirte | Principio DRY (Don’t Repeat Yourself): si algo cambia, lo cambias en un solo lugar. |
| Organizar | Divides un problema grande en tareas pequeñas con nombres claros. |
| Probar | Puedes verificar cada función por separado. |
| Leer | area_circulo(5) dice qué hace; math.pi * 5 ** 2 dice cómo. |
Es la misma idea que una función en matemáticas: \(f(x) = x^2 + 1\) recibe un valor \(x\) y regresa un resultado.
def nombre_funcion(parametro_1, parametro_2):
"""Descripción breve de lo que hace la función."""
resultado = parametro_1 + parametro_2 # Cuerpo de la función
return resultado # Valor que regresa| Parte | ¿Qué es? |
|---|---|
def |
Palabra reservada para definir una función. |
nombre_funcion |
El nombre de la función (en snake_case, como las variables). |
(parametro_1, parametro_2) |
Los parámetros: los datos que recibe. Puede no tener ninguno: (). |
: |
Los dos puntos, como en if y for. |
"""...""" |
El docstring: la documentación de la función (opcional, pero muy recomendable). |
| Cuerpo | Las instrucciones, con 4 espacios de indentación. |
return |
Termina la función y regresa un valor a quien la llamó. |
Definir una función no la ejecuta: solo la guarda con ese nombre. Para ejecutarla hay que llamarla, escribiendo su nombre con paréntesis:
def saludar(nombre):
return "¡Hola, " + nombre + "!"
mensaje = saludar("Ana") # Llamar a la función
print(mensaje)¡Hola, Ana!
print(saludar("Luis"))¡Hola, Luis!
Una función puede no recibir parámetros:
def mostrar_bienvenida():
print("Bienvenida al Taller de Cómputo")
mostrar_bienvenida()Bienvenida al Taller de Cómputo
La función debe definirse antes de llamarla. Si la llamas antes de la línea def, obtendrás un NameError.
def potencia(base, exponente).potencia(2, 10).def potencia(base, exponente):
return base ** exponente
print(potencia(2, 10)) # Por posición: base=2, exponente=101024
print(potencia(exponente=10, base=2)) # Por nombre: el orden no importa1024
print(potencia(10, 2)) # ¡Cuidado! base=10, exponente=2100
Pasar los argumentos por nombre hace el código más claro cuando hay varios parámetros.
Un parámetro puede tener un valor por defecto, que se usa si no se envía ese argumento:
def potencia(base, exponente=2):
return base ** exponente
print(potencia(5)) # Usa exponente=2 -> 2525
print(potencia(5, 3)) # Usa exponente=3 -> 125125
Ya usamos valores por defecto sin saberlo: print() tiene sep=" " y end="\n", y round() tiene ndigits=None.
Los parámetros con valor por defecto van al final: def f(a, b=2) es correcto, pero def f(a=1, b) produce un SyntaxError.
return vs. print()Es la confusión más común al empezar:
print() |
return |
|
|---|---|---|
| ¿Qué hace? | Muestra un valor en la pantalla. | Regresa un valor a quien llamó la función. |
| ¿Se puede guardar el resultado? | No. | Sí: x = funcion(). |
| ¿Termina la función? | No. | Sí, inmediatamente. |
def doble_con_print(x):
print(x * 2)
def doble_con_return(x):
return x * 2
a = doble_con_print(5) # Muestra 10, pero no regresa nada10
b = doble_con_return(5) # No muestra nada, regresa 10
print("a =", a) # Nonea = None
print("b =", b) # 10b = 10
print(b + 1) # Podemos seguir usando el resultado11
Una función sin return regresa None. Por eso, las funciones que calculan algo deben usar return, y dejar el print() para quien las llama.
Una función puede regresar varios valores separados por comas. Python los agrupa en una tupla, que podemos desempaquetar:
def division_entera(dividendo, divisor):
cociente = dividendo // divisor
residuo = dividendo % divisor
return cociente, residuo
cociente, residuo = division_entera(17, 5)
print("Cociente:", cociente, " Residuo:", residuo)Cociente: 3 Residuo: 2
print(division_entera(17, 5)) # (3, 2): una tupla(3, 2)
return termina la funciónDespués de return, la función termina, aunque haya más líneas. Esto permite salir en cuanto conocemos la respuesta:
def es_primo(n):
if n < 2:
return False
for divisor in range(2, int(n ** 0.5) + 1):
if n % divisor == 0:
return False # Encontramos un divisor: ya no es primo
return True # No hubo divisores
print([n for n in range(1, 30) if es_primo(n)])[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Compara con el ejercicio de números primos del capítulo de ciclos: return reemplaza a la combinación de una variable es_primo y break.
Un docstring es un texto entre triples comillas justo debajo de la línea def. Explica qué hace la función, qué recibe y qué regresa. Es lo que muestra help():
def area_triangulo(base, altura):
"""Calcula el área de un triángulo.
Parámetros:
base (float): longitud de la base.
altura (float): altura del triángulo.
Regresa:
float: el área, base * altura / 2.
"""
return base * altura / 2
help(area_triangulo)Help on function area_triangulo in module __main__:
area_triangulo(base, altura)
Calcula el área de un triángulo.
Parámetros:
base (float): longitud de la base.
altura (float): altura del triángulo.
Regresa:
float: el área, base * altura / 2.
Las anotaciones de tipo (type hints) indican qué tipo de dato espera cada parámetro y qué tipo regresa la función. Python no las verifica, pero hacen el código más claro:
def promedio(datos: list) -> float:
"""Regresa el promedio de una lista de números."""
return sum(datos) / len(datos)
print(promedio([8, 9, 10]))9.0
datos: list significa “se espera una lista” y -> float significa “regresa un decimal”.
Las variables creadas dentro de una función son locales: solo existen mientras la función se ejecuta.
def calcular_iva(precio):
tasa = 0.16 # Variable local
return precio * tasa
print(calcular_iva(100))16.0
print(tasa) # NameError: tasa solo existía dentro de la funciónNameError: name 'tasa' is not defined
Una función puede leer una variable global (definida fuera de todas las funciones), pero depender de ella es una mala práctica: es mejor pasarla como parámetro.
TASA_IVA = 0.16 # Constante global
def calcular_iva(precio, tasa=TASA_IVA):
return precio * tasa
print(calcular_iva(100))16.0
print(calcular_iva(100, tasa=0.08))8.0
| Variable local | Variable global | |
|---|---|---|
| ¿Dónde se crea? | Dentro de una función. | Fuera de todas las funciones. |
| ¿Dónde existe? | Solo dentro de esa función. | En todo el programa. |
| Recomendación | Úsalas libremente. | Úsalas solo para constantes (MAYUSCULAS). |
Una función no sabe con qué datos la llamarán. Siguiendo lo que vimos en el capítulo de buenas prácticas, una buena función valida sus argumentos al inicio y lanza un error claro con raise si son incorrectos:
def factorial(n):
"""Regresa n! para un entero n >= 0."""
if not isinstance(n, int):
raise TypeError("n debe ser un número entero")
if n < 0:
raise ValueError("n no puede ser negativo; se recibió " + str(n))
resultado = 1
for i in range(2, n + 1):
resultado *= i
return resultado
print(factorial(5))120
print(factorial(-3))ValueError: n no puede ser negativo; se recibió -3
Quien llama a la función puede atrapar el error con try / except:
try:
print(factorial(-3))
except ValueError as error:
print("No se pudo calcular:", error)No se pudo calcular: n no puede ser negativo; se recibió -3
Una función se prueba llamándola con valores cuya respuesta ya conocemos. Con assert, la prueba no muestra nada si todo está bien y se detiene si algo falla:
assert factorial(0) == 1
assert factorial(1) == 1
assert factorial(5) == 120
assert es_primo(2) and es_primo(97)
assert not es_primo(1) and not es_primo(91)
print("Todas las pruebas pasaron ✅")Todas las pruebas pasaron ✅
Escribe las pruebas al mismo tiempo que la función e incluye los casos límite: 0, 1, números negativos, listas vacías. Si más adelante modificas la función, vuelve a ejecutar las pruebas para verificar que no rompiste nada.
lambdaEn Python, las funciones también son objetos: se pueden guardar en variables y enviar como argumentos a otras funciones. Ya lo hicimos en la 🐍 Actividad 2 con max(set(datos), key=datos.count).
def ultimo_digito(n):
return n % 10
numeros = [23, 51, 17, 40, 88]
print(sorted(numeros, key=ultimo_digito)) # Ordenar por el último dígito[40, 51, 23, 17, 88]
Una función lambda es una función pequeña, de una sola expresión y sin nombre, útil para estos casos:
print(sorted(numeros, key=lambda n: n % 10))[40, 51, 23, 17, 88]
cuadrado = lambda x: x ** 2 # Funciona, pero para esto es mejor usar def
print(cuadrado(9))81
Usa lambda solo para funciones muy cortas que pasas como argumento; si necesitas un nombre, usa def.
Una función recursiva se llama a sí misma. Es útil para definiciones matemáticas recursivas, como el factorial: \(n! = n \cdot (n-1)!\) con \(0! = 1\).
def factorial_recursivo(n):
if n == 0: # Caso base: detiene la recursión
return 1
return n * factorial_recursivo(n - 1) # Caso recursivo
print(factorial_recursivo(5)) # 5 * 4 * 3 * 2 * 1 * 1 = 120120
Toda función recursiva necesita un caso base; sin él, se llamaría a sí misma indefinidamente hasta producir un RecursionError.
| Práctica | Mal | Bien |
|---|---|---|
Nombres con verbos en snake_case. |
def x(a): |
def calcular_promedio(datos): |
| Una sola tarea por función. | Una función que lee datos, calcula y grafica. | Tres funciones pequeñas. |
| Regresar valores en lugar de imprimirlos. | print(resultado) dentro de la función. |
return resultado y que quien la llama decida. |
| Documentar con un docstring. | Sin explicación. | """Calcula el área de un círculo.""" |
| Pasar los datos como parámetros. | Usar variables globales dentro de la función. | def calcular_iva(precio, tasa): |
| Validar los argumentos. | Dejar que falle con un error confuso. | raise ValueError("n no puede ser negativo") |
| Funciones cortas. | 100 líneas en una función. | Si no cabe en la pantalla, divídela. |
| Probar con casos conocidos. | Confiar en que funciona. | assert factorial(5) == 120 |
| No usar listas como valor por defecto. | def agregar(x, lista=[]): |
def agregar(x, lista=None): (ver ejercicio 7). |
| Error | Ejemplo | Corrección |
|---|---|---|
| Olvidar los paréntesis al llamar | area_circulo (muestra <function ...>) |
area_circulo(5) |
Olvidar el return |
La función regresa None. |
Agrega return resultado. |
| Llamar antes de definir | NameError: name 'f' is not defined |
Escribe el def antes de la llamada. |
| Número incorrecto de argumentos | TypeError: potencia() missing 1 required positional argument |
Revisa cuántos parámetros recibe. |
| Usar una variable local afuera | NameError |
Regrésala con return. |
| Olvidar los dos puntos o la indentación | SyntaxError / IndentationError |
def f(x): y cuerpo con 4 espacios. |
Llamar sum, max, list… a una función o variable |
Se pierde la función integrada. | Usa nombres descriptivos. |
Resuelve los ejercicios en los editores interactivos. Recuerda que lo que escribas no se guarda al recargar la página. En estos ejercicios, Run Code prueba tu función con varios valores, no solo con los del ejemplo.
Completa la función area_rectangulo(base, altura) para que regrese el área del rectángulo.
Escribe return base * altura. Si usas print(), la función mostrará el valor, pero regresará None.
def area_rectangulo(base, altura):
"""Regresa el área de un rectángulo."""
return base * altura
print(area_rectangulo(3, 4)) # 12Escribe la función celsius_a_fahrenheit(celsius) que convierta una temperatura con la fórmula \(F = \frac{9}{5} C + 32\). Incluye un docstring.
El docstring va en la primera línea del cuerpo: """Convierte grados Celsius a Fahrenheit.""". Después: return 9 / 5 * celsius + 32.
def celsius_a_fahrenheit(celsius):
"""Convierte una temperatura de grados Celsius a grados Fahrenheit."""
return 9 / 5 * celsius + 32
print(celsius_a_fahrenheit(0)) # 32.0
print(celsius_a_fahrenheit(100)) # 212.0Escribe la función es_par(n) que regrese True si n es par y False si es impar. Intenta escribirla en una sola línea de cuerpo, sin if.
La expresión n % 2 == 0 ya es True o False: puedes regresarla directamente con return.
def es_par(n):
"""Regresa True si n es par."""
return n % 2 == 0
print(es_par(4), es_par(7)) # True FalseEs mejor que escribir if n % 2 == 0: return True y else: return False: hace lo mismo con menos código.
Completa la función factorial(n) para que:
ValueError si n es negativo.for y lo regrese.La condición es n < 0. Dentro del ciclo, acumula el producto con resultado *= i.
def factorial(n):
"""Regresa n! para un entero n >= 0."""
if n < 0:
raise ValueError("n no puede ser negativo")
resultado = 1
for i in range(2, n + 1):
resultado *= i
return resultado
print(factorial(5)) # 120Escribe la función estadisticas(datos) que regrese tres valores: la media, el mínimo y el máximo de la lista. Después desempaqueta el resultado en tres variables.
Usa las funciones integradas sum(), len(), min() y max(), y regresa los tres valores separados por comas: return media, minimo, maximo.
def estadisticas(datos):
"""Regresa la media, el mínimo y el máximo de una lista de números."""
media = sum(datos) / len(datos)
return media, min(datos), max(datos)
media, minimo, maximo = estadisticas([4, 8, 15, 16, 23, 42])
print("Media:", media, " Mínimo:", minimo, " Máximo:", maximo)Escribe la función distancia(p, q) que reciba dos puntos como tuplas (x, y) y regrese la distancia entre ellos:
\[d(p, q) = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}\]
Usa anotaciones de tipo y un docstring.
La raíz cuadrada es elevar a 0.5: return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5. En el docstring describe qué hace la función.
def distancia(p: tuple, q: tuple) -> float:
"""Regresa la distancia euclidiana entre los puntos p = (x1, y1) y q = (x2, y2)."""
x1, y1 = p
x2, y2 = q
return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
print(distancia((0, 0), (3, 4))) # 5.0Python también incluye esta función: math.dist((0, 0), (3, 4)).
La función agregar_calificacion() debería crear una lista nueva cada vez que no se le pasa una lista. Ejecuta el código: ¿por qué la segunda llamada también contiene el 9?
Corrígela usando None como valor por defecto.
El valor por defecto [] se crea una sola vez, al definir la función, y todas las llamadas comparten la misma lista (las listas son mutables). Usa lista=None y, dentro de la función, if lista is None: lista = [].
def agregar_calificacion(calificacion, lista=None):
if lista is None:
lista = [] # Una lista nueva en cada llamada
lista.append(calificacion)
return lista
print(agregar_calificacion(9)) # [9]
print(agregar_calificacion(7)) # [7]Es el mismo fenómeno de la 🐍 Actividad 1: dos nombres que apuntan al mismo objeto mutable.
En el capítulo de ciclos aproximamos \(\sqrt{2}\) con el método de Newton usando un ciclo while. Conviértelo en una función reutilizable que siga las buenas prácticas:
raiz_newton(n, tolerancia=1e-10) y tiene un docstring.ValueError si n es negativo.n es 0, regresa 0.x = n y repite \(x = x - \frac{x^2 - n}{2x}\) mientras \(|x^2 - n| >\) tolerancia.x.Primero las validaciones: if n < 0: raise ValueError(...) y if n == 0: return 0. Después, x = n y el ciclo while abs(x ** 2 - n) > tolerancia: con la fórmula de Newton.
def raiz_newton(n, tolerancia=1e-10):
"""Aproxima la raíz cuadrada de n >= 0 con el método de Newton.
Parámetros:
n (float): número no negativo.
tolerancia (float): error máximo permitido en |x**2 - n|.
Regresa:
float: aproximación de la raíz cuadrada de n.
"""
if n < 0:
raise ValueError("n no puede ser negativo")
if n == 0:
return 0
x = n
while abs(x ** 2 - n) > tolerancia:
x = x - (x ** 2 - n) / (2 * x)
return x
print(raiz_newton(2)) # 1.4142135623746899
print(raiz_newton(9)) # 3.0Ahora el método se puede usar con cualquier número, se prueba fácilmente y el mensaje de error explica qué salió mal. Extra: escribe pruebas con assert comparando con math.sqrt().
| Tema | Idea clave |
|---|---|
| Definir | def nombre(parametros): con el cuerpo indentado 4 espacios. |
| Llamar | nombre(argumentos); definir no es lo mismo que ejecutar. |
return |
Regresa un valor y termina la función; sin return, la función regresa None. |
return vs. print() |
print() muestra; return entrega el valor para seguir usándolo. |
| Parámetros | Por posición, por nombre y con valores por defecto (al final). |
| Documentación | Docstring con """...""" y anotaciones de tipo def f(x: int) -> float:. |
| Alcance | Las variables de una función son locales; usa parámetros en lugar de globales. |
| Programación defensiva | Valida los argumentos y usa raise con mensajes claros. |
| Pruebas | assert con casos conocidos y casos límite. |
lambda |
Funciones cortas sin nombre, útiles como argumento (key=). |
| Recursión | Una función que se llama a sí misma; necesita un caso base. |