61  Funciones en Python

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:

61.1 ¿Por qué crear funciones?

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 ** 2

Si 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:

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
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.

61.2 Definir y llamar una función

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:

Código
def saludar(nombre):
    return "¡Hola, " + nombre + "!"

mensaje = saludar("Ana")     # Llamar a la función
print(mensaje)
¡Hola, Ana!
Código
print(saludar("Luis"))
¡Hola, Luis!

Una función puede no recibir parámetros:

Código
def mostrar_bienvenida():
    print("Bienvenida al Taller de Cómputo")

mostrar_bienvenida()
Bienvenida al Taller de Cómputo
Importante

La función debe definirse antes de llamarla. Si la llamas antes de la línea def, obtendrás un NameError.

61.3 Parámetros y argumentos

  • Los parámetros son los nombres que aparecen en la definición: def potencia(base, exponente).
  • Los argumentos son los valores que se envían al llamarla: potencia(2, 10).

61.3.1 Argumentos por posición y por nombre

Código
def potencia(base, exponente):
    return base ** exponente

print(potencia(2, 10))                     # Por posición: base=2, exponente=10
1024
Código
print(potencia(exponente=10, base=2))      # Por nombre: el orden no importa
1024
Código
print(potencia(10, 2))                     # ¡Cuidado! base=10, exponente=2
100

Pasar los argumentos por nombre hace el código más claro cuando hay varios parámetros.

61.3.2 Valores por defecto

Un parámetro puede tener un valor por defecto, que se usa si no se envía ese argumento:

Código
def potencia(base, exponente=2):
    return base ** exponente

print(potencia(5))        # Usa exponente=2 -> 25
25
Código
print(potencia(5, 3))     # Usa exponente=3 -> 125
125

Ya usamos valores por defecto sin saberlo: print() tiene sep=" " y end="\n", y round() tiene ndigits=None.

Advertencia

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.

61.4 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.
Código
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 nada
10
Código
b = doble_con_return(5)    # No muestra nada, regresa 10

print("a =", a)            # None
a = None
Código
print("b =", b)            # 10
b = 10
Código
print(b + 1)               # Podemos seguir usando el resultado
11

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.

61.4.1 Regresar varios valores

Una función puede regresar varios valores separados por comas. Python los agrupa en una tupla, que podemos desempaquetar:

Código
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
Código
print(division_entera(17, 5))      # (3, 2): una tupla
(3, 2)

61.4.2 return termina la función

Después de return, la función termina, aunque haya más líneas. Esto permite salir en cuanto conocemos la respuesta:

Código
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.

61.5 Documentar funciones

61.5.1 Docstrings

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():

Código
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.

61.5.2 Anotaciones de tipo

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:

Código
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”.

61.6 Alcance de las variables

Las variables creadas dentro de una función son locales: solo existen mientras la función se ejecuta.

Código
def calcular_iva(precio):
    tasa = 0.16                 # Variable local
    return precio * tasa

print(calcular_iva(100))
16.0
Código
print(tasa)                     # NameError: tasa solo existía dentro de la función
NameError: 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.

Código
TASA_IVA = 0.16                 # Constante global

def calcular_iva(precio, tasa=TASA_IVA):
    return precio * tasa

print(calcular_iva(100))
16.0
Código
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).

61.7 Programación defensiva en funciones

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:

Código
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
Código
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:

Código
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

61.8 Probar funciones

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:

Código
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 ✅
Tip

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.

61.9 Funciones como argumentos y funciones lambda

En 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).

Código
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:

Código
print(sorted(numeros, key=lambda n: n % 10))
[40, 51, 23, 17, 88]
Código
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.

61.10 Recursión

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\).

Código
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 = 120
120

Toda función recursiva necesita un caso base; sin él, se llamaría a sí misma indefinidamente hasta producir un RecursionError.

61.11 Buenas prácticas al escribir funciones

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).

61.12 Errores comunes

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.

61.13 Ejercicios

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.

61.13.1 Ejercicio 1. Área de un rectángulo

Completa la función area_rectangulo(base, altura) para que regrese el área del rectángulo.

NotaPista

Escribe return base * altura. Si usas print(), la función mostrará el valor, pero regresará None.

TipSolución
def area_rectangulo(base, altura):
    """Regresa el área de un rectángulo."""
    return base * altura

print(area_rectangulo(3, 4))   # 12

61.13.2 Ejercicio 2. De Celsius a Fahrenheit

Escribe la función celsius_a_fahrenheit(celsius) que convierta una temperatura con la fórmula \(F = \frac{9}{5} C + 32\). Incluye un docstring.

NotaPista

El docstring va en la primera línea del cuerpo: """Convierte grados Celsius a Fahrenheit.""". Después: return 9 / 5 * celsius + 32.

TipSolución
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.0

61.13.3 Ejercicio 3. ¿Es par?

Escribe 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.

NotaPista

La expresión n % 2 == 0 ya es True o False: puedes regresarla directamente con return.

TipSolución
def es_par(n):
    """Regresa True si n es par."""
    return n % 2 == 0

print(es_par(4), es_par(7))   # True False

Es mejor que escribir if n % 2 == 0: return True y else: return False: hace lo mismo con menos código.

61.13.4 Ejercicio 4. Factorial con validación

Completa la función factorial(n) para que:

  1. Lance un ValueError si n es negativo.
  2. Calcule \(n!\) con un ciclo for y lo regrese.
NotaPista

La condición es n < 0. Dentro del ciclo, acumula el producto con resultado *= i.

TipSolución
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))   # 120

61.13.5 Ejercicio 5. Estadísticas de una lista

Escribe 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.

NotaPista

Usa las funciones integradas sum(), len(), min() y max(), y regresa los tres valores separados por comas: return media, minimo, maximo.

TipSolución
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)

61.13.6 Ejercicio 6. Distancia entre dos puntos

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.

NotaPista

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.

TipSolució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.0

Python también incluye esta función: math.dist((0, 0), (3, 4)).

61.13.7 Ejercicio 7. Detective: el valor por defecto que se “acuerda”

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.

NotaPista

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 = [].

TipSolución
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.

61.14 Reto final: raíz cuadrada con el método de Newton

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:

  1. Se llama raiz_newton(n, tolerancia=1e-10) y tiene un docstring.
  2. Lanza un ValueError si n es negativo.
  3. Si n es 0, regresa 0.
  4. En otro caso, empieza en x = n y repite \(x = x - \frac{x^2 - n}{2x}\) mientras \(|x^2 - n| >\) tolerancia.
  5. Regresa x.
NotaPista

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.

TipSolución
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.0

Ahora 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().

61.15 Resumen

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.

61.16 Referencias