3.3 · Funciones avanzadas : decoradores, generadores, lambdas, map/filter

Nivel 3 · Python intermedio

3.3Funciones avanzadas : decoradores, generadores, lambdas, map/filter

Objetivo: escribir Python idiomático y eficiente (lambdas, map/filter, generadores yield, decoradores @) — las herramientas del código profesional y de los frameworks.
Tiempo estimado: 16 min · lectura autónoma · Si solo tienes 5 min: lee «Lo esencial en 30s» + el esquema.

Lo esencial en 30 segundos

  • Lambda = función anónima desechable de una línea : key=lambda t: t[1] para ordenar — def en cuanto se nombra o se reutiliza.
  • El decorador envuelve sin tocar : @chrono añade medida, log o control recibiendo la función y devolviendo una envoltura — con functools.wraps.
  • El generador produce bajo demanda : yield trata un archivo inmenso en memoria constante — listas para lo pequeño, generadores para lo grande.

Mini-caso hilado — Nabil pasa a lo pitónico

Nabil, analista de datos junior en Alger, ordena sus ventas con funciones de tres líneas de un solo uso y carga una exportación de varios GB en lista — su puesto se arrastra. Ordena en una línea (key=lambda v: v["montant"]), compara map/filter con las comprensiones, lee la exportación línea a línea vía un generador yield, y mide sus tratamientos con un decorador @chrono con functools.wraps. Mismos datos, código corto y puesto aliviado.

Este mismo caso vuelve en el curso, el esquema, el guion y el reto 48h.

1. Lambda, map/filter : el pequeño utillaje

La lambda es una función anónima de una línea, hecha para el uso desechable en argumento : sorted(ventes, key=lambda v: v["montant"]) ordena con criterio sin nombrar una función. map aplica una función a cada elemento, filter solo guarda los que cumplen una condición — pero la comprensión ([f(x) for x in xs if ok(x)]) suele ser más legible en Python.

Regla de Nabil : lambda para lo desechable corto, def en cuanto se nombra, se reutiliza o supera una línea. Una lambda de cinco líneas es un def disfrazado : nombrarlo, documentarlo, probarlo. Este utillaje se cruza en todas partes (claves de orden, callbacks) — conocerlo es leer con fluidez el código ajeno.

2. Decoradores y generadores : envolver y aligerar

El decorador enriquece una función sin modificar su código : la recibe, devuelve una envoltura que añade medida, log, caché o control, vía la sintaxis @decorateur. Siempre con functools.wraps para preservar nombre y docstring. Nabil escribe @chrono que muestra el tiempo de ejecución — y reconoce el mecanismo tras @app.route o @pytest.fixture.

El generador (función con yield) produce sus valores uno a uno, bajo demanda : suspendido en cada yield, retomado en la llamada siguiente. Una exportación de varios GB se lee línea por línea en memoria constante en lugar de explotar la RAM. Listas para lo pequeño reutilizado, generadores para lo grande o lo infinito : es la elección que escala.

Error frecuente de interpretación: « Lambda en todas partes para acortar, decorador = magia, lista = siempre ». No. La lambda larga se vuelve ilegible — nombrad un def ; el decorador sin wraps pierde nombre y doc y rompe la introspección ; la lista gigante carga todo en memoria. Desechable en lambda, transversal en decorador con wraps, grande en generador : la herramienta justa, no la más corta.
Funciones avanzadas : la herramienta justa Desechable en lambda, transversal en decorador, grande en generador lambda (desechable)Orden en una línea, def si no+Decorador (transversal)Envoltura + wraps, sin tocar+yield (perezoso)Un elemento a la vez, memoria constante Listas para lo pequeño, generadores para lo grande.
Desechable en lambda, transversal en decorador, grande en generador
Versión en texto del esquema (SEO / lectores de pantalla / traducción): 1) Desechable : Nabil ordena sus ventas por importe con key=lambda en una línea, sin función nombrada. 2) Transversal : su @chrono envuelve cada tratamiento para medir, sin tocar su cuerpo, con wraps. 3) Grande : la exportación de varios GB se lee vía yield, una línea a la vez. Lección : cada forma tiene su terreno — corto, transversal, voluminoso.

La lista que se comió la RAM

« Mi exportación de 8 GB en lista : el puesto calentó toda la tarde en Alger. »
Generador aprendido esa misma noche, ventilador en reposo

Gran flujo, pequeño yield : la memoria os lo agradece.

🔎 Ejemplo concreto — Cronometrar sin tocar + leer sin cargar

Nabil mide su tratamiento y lee una gran exportación línea a línea.

import time
import functools

def chrono(f):
    @functools.wraps(f)
    def enveloppe(*args, **kwargs):
        debut = time.perf_counter()
        resultat = f(*args, **kwargs)
        duree = time.perf_counter() - debut
        print(f"{f.__name__}: {duree:.2f}s")
        return resultat
    return enveloppe

def lignes(fichier):
    with open(fichier, encoding="utf-8") as f:
        for ligne in f:
            yield ligne.strip()

Lectura : @chrono mide sin tocar el cuerpo ; yield solo carga una línea a la vez.

Lección : wraps preserva nombre y docstring de la función decorada.

Vocabulario de la sección

Lambda
Función anónima de una línea (lambda x: x * 2), útil para un tratamiento corto pasado en argumento.

Ejemplo sencillo: El key=lambda v: v["montant"] de la orden de Nabil.

Lo que no es: una función nombrada reutilizada y documentada.

Reflejo: lambda desechable, def en cuanto se nombra.

map / filter
map aplica una función a cada elemento ; filter solo guarda los que cumplen una condición.

Ejemplo sencillo: map(lambda x: x ** 2, montants) y luego filter de los pares en casa de Nabil.

Lo que no es: una comprensión a menudo más legible.

Reflejo: comparar con la comprensión antes de elegir.

Generador
Función con yield que produce sus valores uno a uno, bajo demanda : memoria mínima, ideal grandes flujos.

Ejemplo sencillo: Las líneas de exportación leídas una por una por Nabil.

Lo que no es: una lista gigante cargada de golpe en RAM.

Reflejo: yield en cuanto es grande o infinito.

yield
Devuelve un valor suspendiendo la función, retomada en la llamada siguiente : el corazón de los generadores.

Ejemplo sencillo: Cada yield ligne.strip() de la exportación de Nabil.

Lo que no es: un return que termina la función.

Reflejo: iterar sin cargarlo todo.

Decorador
Función que envuelve otra para añadir un comportamiento (chrono, log, control), vía @decorateur y functools.wraps.

Ejemplo sencillo: El @chrono de Nabil sobre sus tratamientos.

Lo que no es: copiar el código de medida en cada función.

Reflejo: factorizar lo transversal en decorador.

En la práctica — Escribir Python pitónico

  1. Ordenad tuplas por 2º elemento con sorted(..., key=lambda t: t[1]) sobre las ventas de Nabil.
  2. Transformad con map(lambda x: x ** 2, nombres) y filtrad con filter(lambda x: x % 2 == 0, nombres), luego comparad con las comprensiones.
  3. Escribid un generador con yield que produzca los pares al infinito ; consumid 10 con break.
  4. Cread un decorador @chrono con functools.wraps que muestre el tiempo de ejecución, luego aplicadlo.
Manejáis lambdas, map/filter, generadores y decoradores : código idiomático, conciso y eficaz.

Guion listo para decir — presentar su código pitónico (30 s)

« Orden en lambda desechable sobre el importe, generador yield para la exportación voluminosa en memoria constante, decorador chrono con wraps para medir sin tocar. Map y filter comparados con las comprensiones caso por caso. »

Para decir en revisión ; mostrar el @chrono y el yield.

Modelo copiar-pegar — decorador chrono + generador

import time
import functools

def chrono(f):
    @functools.wraps(f)
    def enveloppe(*args, **kwargs):
        debut = time.perf_counter()
        resultat = f(*args, **kwargs)
        print(f"{f.__name__}: {time.perf_counter() - debut:.2f}s")
        return resultat
    return enveloppe

@chrono
def total(montants):
    return sum(montants)

ventes_triees = sorted([{"v": "Alger", "m": 9}, {"v": "Oran", "m": 4}], key=lambda v: v["m"])

Independiente

Un @chrono + un orden en lambda basta para seguir el rendimiento de los informes.

PYME en Alger

Nabil lee la exportación cajas de varios GB en generador sin cambiar de puesto.

Equipo de producto

Imponer wraps en todo decorador para mantener nombres y docs explotables.

Principiante

Esta semana : un orden en lambda y un generador de pares al infinito.

✓ Buenas prácticas

  • Reservar lambda para lo desechable corto ; nombrar un def en cuanto se reutiliza.
  • Añadir functools.wraps en todo decorador (nombre y doc preservados).
  • Pasar a yield en cuanto los datos son grandes o infinitos.

✗ Errores frecuentes

  • Escribir una lambda de cinco líneas ilegible en lugar de un def.
  • Olvidar wraps (nombre perdido, introspección rota).
  • Cargar un archivo gigante en lista en lugar de un generador.

Puntos clave

  • LAMBDA = función anónima de UNA línea, en argumento (key de sorted…) ; si no, def.
  • DECORADOR = envoltura SIN tocar (@ + wraps) : medida, log, control factorizados.
  • GENERADOR (yield) = producción BAJO DEMANDA, memoria constante : la elección de lo grande.
  • Estas herramientas están EN TODAS PARTES en los frameworks : manejarlas es leer el código de los pros.

Reto 48h — aplica mañana

Escribid 1 decorador @chrono con wraps y 1 generador yield sobre vuestros propios datos.

Lista de salida de la sección
  • Ordeno en lambda sin función desechable nombrada.
  • Mi decorador preserva nombre y doc vía wraps.
  • Mi gran flujo pasa en generador, no en lista.
Autoevaluación 1-5 : ¿cabe cada lambda en una línea legible?¿pasa aún un gran tratamiento por una lista?

Preguntas frecuentes

¿Cuándo preferir un generador a una lista?

Cuando los datos son voluminosos o infinitos : una lista carga todo en memoria, un generador produce un valor a la vez. Para leer un archivo de varios GB línea por línea, el generador es indispensable ; para unos pocos elementos reutilizados, una lista basta.

Un decorador, ¿para qué sirve en concreto?

Para añadir un comportamiento repetitivo (chrono, log, control, caché) a varias funciones sin recopiarlo en todas partes. Ya usáis algunos : @app.route, @staticmethod, @pytest.fixture — mismo mecanismo, misma elegancia.

Comprueba tu comprensión

¿Por qué functools.wraps en un decorador?

Recursos adicionales (opcional, para ir más allá)

Examen del nivel 3Preguntas inéditas y más difíciles para validar tus conocimientos