Audiencia. Personas que ya han visto algo de programación y quieren repasar Python de forma práctica.
Prerrequisitos. Tipos, estructuras de datos y control del flujo.
Duración orientativa. 60–90 minutos, incluida la práctica.
Objetivos de aprendizaje
- Definir funciones pequeñas con entradas y salidas claras.
- Manejar parámetros posicionales, nombrados, variables y predeterminados.
- Comprender alcance, anotaciones, funciones de orden superior y cierres.
- Reconocer decoradores, generadores y recursión.
Itinerario
- Definición y retorno
- Parámetros
- Alcance
- Funciones como valores
- Decoradores
- Generadores
- Recursión
- Práctica
Cómo trabajar: ejecuta las celdas en orden, predice el resultado antes de verlo y modifica los ejemplos. Cada bloque termina con un ejercicio, un punto de partida y una solución posible.
1. Definir, llamar y devolver
Una función agrupa una responsabilidad. return entrega un resultado y termina la ejecución; sin return explícito, devuelve None. Una función puede devolver varios valores empaquetados en una tupla.
def area_rectangulo(ancho, alto):
return ancho * alto
area = area_rectangulo(4, 2.5)
print(area)
def cociente_y_resto(dividendo, divisor):
return dividendo // divisor, dividendo % divisor
cociente, resto = cociente_y_resto(17, 5)
print(cociente, resto)
10.0 3 2
2. Parámetros y argumentos
Los parámetros aparecen en la definición; los argumentos son los valores usados al llamar. Los argumentos con nombre mejoran la lectura. Los valores predeterminados deben ir tras los obligatorios.
def presentar(nombre, saludo="Hola", puntuacion=None):
texto = f"{saludo}, {nombre}"
if puntuacion is not None:
texto += f". Puntuación: {puntuacion}"
return texto
print(presentar("Irene"))
print(presentar(nombre="Irene", saludo="Bienvenida", puntuacion=9))
Hola, Irene Bienvenida, Irene. Puntuación: 9
Trampa importante: un valor predeterminado mutable se crea una sola vez. Usa None y crea la colección dentro.
def agregar_evento(evento, historial=None):
if historial is None:
historial = []
historial.append(evento)
return historial
print(agregar_evento("inicio"))
print(agregar_evento("fin")) # lista independiente
['inicio'] ['fin']
/ marca parámetros solo posicionales y * marca los que deben escribirse por nombre. *args reúne argumentos posicionales extra en una tupla; **kwargs, argumentos nombrados extra en un diccionario.
def convertir(valor, /, *, factor=1.0, desplazamiento=0.0):
return valor * factor + desplazamiento
def resumen(*valores, etiqueta="datos", **metadatos):
return {
"etiqueta": etiqueta,
"cantidad": len(valores),
"suma": sum(valores),
"metadatos": metadatos,
}
print(convertir(20, factor=1.8, desplazamiento=32))
print(resumen(3, 5, 8, etiqueta="serie", unidad="kg"))
68.0
{'etiqueta': 'serie', 'cantidad': 3, 'suma': 16, 'metadatos': {'unidad': 'kg'}}
3. Documentación, tipos y alcance
Una docstring explica el contrato. Las anotaciones de tipo ayudan a lectores y herramientas, pero Python no las valida automáticamente. El alcance sigue la regla LEGB: local, función envolvente, global y built-ins.
TASA_IVA = 0.21
def precio_final(precio: float, descuento: float = 0.0) -> float:
'''Calcula el precio tras descuento e IVA; los porcentajes van de 0 a 1.'''
base = precio * (1 - descuento)
return base * (1 + TASA_IVA)
print(f"{precio_final(100, 0.10):.2f} €")
print(precio_final.__annotations__)
108.90 €
{'precio': <class 'float'>, 'descuento': <class 'float'>, 'return': <class 'float'>}
Evita modificar globales desde funciones. Un cierre puede conservar estado de la función envolvente; nonlocal permite actualizarlo de manera explícita.
def crear_contador(inicio=0):
valor = inicio
def incrementar(paso=1):
nonlocal valor
valor += paso
return valor
return incrementar
contador = crear_contador(10)
print(contador(), contador(5), contador())
11 16 17
4. Funciones como valores
Las funciones se pueden guardar, pasar y devolver. Esto permite separar una transformación de los datos sobre los que se aplica. Las lambda son funciones anónimas breves; para lógica compleja, usa def.
def aplicar(operacion, valores):
return [operacion(valor) for valor in valores]
def doble(numero):
return numero * 2
print(aplicar(doble, [1, 2, 3]))
print(sorted([("Ana", 8), ("Leo", 10), ("Eva", 7)], key=lambda item: item[1], reverse=True))
[2, 4, 6]
[('Leo', 10), ('Ana', 8), ('Eva', 7)]
5. Decoradores
Un decorador recibe una función y devuelve otra. Se usa para añadir comportamiento transversal —registro, autorización, caché— sin cambiar el cuerpo original. functools.wraps conserva nombre y documentación.
from functools import wraps
def registrar_llamada(funcion):
@wraps(funcion)
def envoltorio(*args, **kwargs):
print(f"Llamando a {funcion.__name__}")
resultado = funcion(*args, **kwargs)
print(f"Resultado: {resultado}")
return resultado
return envoltorio
@registrar_llamada
def sumar(a, b):
return a + b
sumar(4, 7)
Llamando a sumar Resultado: 11
11
6. Generadores
Una función con yield produce valores de uno en uno y conserva su estado. Es útil para secuencias grandes o potencialmente infinitas porque no necesita almacenarlas completas.
def cuenta_atras(desde):
actual = desde
while actual > 0:
yield actual
actual -= 1
generador = cuenta_atras(4)
print(next(generador))
print(list(generador))
4 [3, 2, 1]
7. Recursión
Una función recursiva se llama a sí misma y necesita un caso base. En Python, una versión iterativa suele ser más eficiente y evita el límite de recursión, pero la recursión expresa bien árboles y problemas divisibles.
def factorial(n):
if n < 0:
raise ValueError("n debe ser no negativo")
if n in (0, 1):
return 1
return n * factorial(n - 1)
print(factorial(6))
720
Errores habituales
- Hacer que una función imprima cuando debería devolver un valor reutilizable.
- Usar listas o diccionarios como valores predeterminados.
- Crear funciones enormes con múltiples responsabilidades.
- Confiar en anotaciones como si validaran datos en ejecución.
Extensión opcional: estudia functools.partial, functools.lru_cache y protocolos de tipado.
Práctica · Canalización de datos
Crea procesar(numeros, transformacion, filtro=None) que:
- conserve todos los números si no se proporciona filtro;
- filtre cuando reciba una función predicado;
- aplique la transformación a los valores restantes;
- devuelva una lista, sin imprimir.
def procesar(numeros, transformacion, filtro=None):
# TODO: filtra opcionalmente y transforma.
pass
Ver una solución posible
def procesar(numeros, transformacion, filtro=None):
seleccionados = numeros if filtro is None else (n for n in numeros if filtro(n))
return [transformacion(n) for n in seleccionados]
resultado = procesar(range(8), lambda n: n ** 2, lambda n: n % 2 == 0)
print(resultado)
assert resultado == [0, 4, 16, 36]
[0, 4, 16, 36]
Comprobación rápida
- [ ] Mis funciones reciben datos y devuelven resultados claros.
- [ ] Sé evitar parámetros mutables compartidos.
- [ ] Puedo explicar para qué sirven cierres, decoradores y generadores.