lección 6
Paradigmas: OOP y funcional en Python
Descubre los tres estilos de programar (imperativo, orientado a objetos, funcional) y por qué importa entenderlos antes de usar Pandas y PySpark.
⏱ 60 min
### El problema: hay más de una forma de organizar tu código
Hasta ahora has escrito código de una forma muy natural: defines variables, usas if/else para decidir, recorres datos con for y llamas a funciones. Este estilo se llama programación imperativa — le dices al ordenador PASO A PASO qué hacer, como si le dieras instrucciones a un cocinero novato: "coge el cuchillo, pela la patata, córtala en cubos, échala a la olla". Funciona perfectamente. Es claro y directo.
Pero resulta que existen otras dos formas de organizar el código que vas a necesitar entender ANTES de avanzar a herramientas más potentes. Cuando uses Pandas y hagas df.groupby("categoria").sum(), estarás usando programación orientada a objetos (OOP). Cuando uses PySpark y hagas df.filter(col("precio") > 100).select("producto"), estarás usando programación funcional. Si no sabes qué es cada cosa, esas líneas serán magia incomprensible. Si lo sabes, serán la consecuencia lógica de lo que ya conoces.
Este capítulo no pretende convertirte en un experto en OOP ni en funcional — para eso hay cursos enteros. Lo que vamos a hacer es darte el MAPA MENTAL para que reconozcas estos patrones cuando los veas, entiendas por qué existen y puedas usarlos con confianza. Es la diferencia entre conducir sabiendo cómo funciona un motor (puedes diagnosticar problemas) y conducir sin saber nada (todo es magia hasta que se rompe).
### Los tres paradigmas: imperativo, OOP y funcional
Un paradigma de programación es una FORMA DE PENSAR sobre cómo resolver problemas con código. No es una tecnología ni una librería — es una filosofía. Y Python es especial porque soporta los tres paradigmas principales al mismo tiempo. Puedes mezclarlos según lo que necesites. Eso lo hace increíblemente flexible, pero también puede confundir si nadie te explica que estás viendo tres estilos diferentes en el mismo lenguaje.
- IMPERATIVO: le dices al ordenador CÓMO hacer algo, paso a paso. "Recorre esta lista, si el precio es mayor a 100, añádelo a otra lista, al final devuelve esa lista." Es lo que has hecho hasta ahora.
- ORIENTADO A OBJETOS (OOP): organizas tu código alrededor de OBJETOS que tienen datos (atributos) y comportamientos (métodos). "El objeto lista SABE ordenarse. El objeto string SABE convertirse a mayúsculas."
- FUNCIONAL: construyes tu programa COMBINANDO funciones que transforman datos. "Filtra esto, luego transforma aquello, luego reduce a un resultado." Sin modificar nada — cada paso produce algo nuevo.
La analogía del restaurante: el estilo imperativo es como darle instrucciones paso a paso a un cocinero novato — "pon la sartén, echa aceite, espera a que caliente, pon la carne". El estilo OOP es como tener utensilios inteligentes — "sarten.calentar()" y la sartén ya sabe qué hacer por dentro. El estilo funcional es como una cadena de montaje — cada estación transforma el plato y lo pasa a la siguiente, sin que nadie vuelva atrás a modificar lo que ya pasó.
### OOP en Python: ya lo estás usando sin saberlo
Aquí viene la revelación que cambia cómo ves Python: TODO en Python es un objeto. Cuando escribes "hola".upper(), estás llamando al MÉTODO upper() del OBJETO string "hola". Cuando haces lista.append(x), estás pidiendo al OBJETO lista que ejecute su MÉTODO append. Cuando usas len(datos), Python internamente llama al método __len__() del objeto datos. Has estado usando OOP desde la primera lección sin darte cuenta.
La idea fundamental de OOP es esta: en vez de tener DATOS por un lado y FUNCIONES por otro, los empaquetas juntos en un OBJETO. El objeto string "hola" CONTIENE los caracteres y SABE cómo transformarse (upper, lower, split, strip...). El objeto lista [1,2,3] CONTIENE los elementos y SABE cómo gestionarse (append, sort, pop...). Los datos y las operaciones sobre esos datos viajan juntos.
1# Todo en Python es un objeto con metodos2texto = " datos de ventas del lunes "34# El objeto string SABE transformarse5print(texto.strip()) # quita espacios6print(texto.upper()) # mayusculas7print(texto.split()) # divide en palabras8print(texto.replace("lunes", "martes")) # sustituye910# El objeto lista SABE gestionarse11ventas = [120.5, 89.0, 245.0, 67.3]12ventas.append(180.0) # se anade a si misma13ventas.sort() # se ordena a si misma14print(ventas) # [67.3, 89.0, 120.5, 180.0, 245.0]1516# El objeto diccionario SABE consultarse17cliente = {"nombre": "Ana", "plan": "premium"}18print(cliente.keys()) # sabe sus claves19print(cliente.get("edad", 0)) # sabe buscar de forma segura20cliente.update({"edad": 28}) # sabe actualizarse
Cada vez que usas un punto (.) después de un valor, estás usando OOP.
Por qué esto importa para tu futuro? Porque TODAS las librerías de datos usan OOP. Cuando escribas df.groupby("cat").sum() en Pandas, df es un objeto DataFrame que tiene un método groupby() que devuelve OTRO objeto sobre el cual llamas sum(). Es una CADENA de métodos — cada uno devuelve un objeto nuevo con más métodos disponibles. Entender que cada punto es "pedirle algo a un objeto" te desbloquea Pandas, PySpark y cualquier librería Python.
Consejo de senior: no necesitas crear tus propias clases para usar OOP en datos. Lo que necesitas es entender que cuando ves algo.metodo().otro_metodo(), son objetos que devuelven objetos. Eso es el 95% de lo que necesitas saber de OOP para trabajar con datos. Crear clases propias es útil, pero es un tema para más adelante.
Y un uso que vas a necesitar constantemente: unir una lista de palabras de vuelta en un solo texto. .split() parte y " ".join() pega — son la pareja:
1palabras = "hola mundo".split() # ['hola', 'mundo']2print(" ".join(palabras)) # 'hola mundo' — un solo espacio34# Sirve para normalizar: partir por los espacios de más y volver a unir limpio5sucio = " datos de ventas "6limpio = " ".join(sucio.strip().split())7print(limpio) # 'datos de ventas'
.split() parte un texto por espacios; " ".join() lo pega de vuelta con un solo espacio.
### Métodos encadenados: el patrón que domina Pandas y PySpark
El patrón más potente de OOP para datos es el ENCADENAMIENTO de métodos (method chaining). La idea: cada método devuelve un objeto, y sobre ese objeto puedes llamar a otro método. Es como una cadena de montaje donde cada estación transforma el producto y lo pasa a la siguiente.
1# Encadenamiento de metodos con strings2nombre_sucio = " ana GARCIA lopez "34# Sin encadenar (verboso)5paso1 = nombre_sucio.strip()6paso2 = paso1.lower()7paso3 = paso2.title()8print(paso3) # "Ana Garcia Lopez"910# Encadenado (una linea — cada metodo devuelve un string nuevo)11nombre_limpio = nombre_sucio.strip().lower().title()12print(nombre_limpio) # "Ana Garcia Lopez"1314# Asi se ve en Pandas (futuro):15# df.dropna().groupby("categoria")["importe"].sum().sort_values(ascending=False)16# | | | |17# quita NaN agrupa suma ordena18#19# Cada punto produce un objeto nuevo con mas metodos disponibles
Cada método devuelve un objeto. Sobre ese objeto llamas al siguiente. Es una cadena.
### Programación funcional: transformar sin mutar
El tercer paradigma es la programación funcional. Su filosofía es radical: NUNCA modificar datos existentes. En vez de cambiar una lista, creas una NUEVA lista con los cambios. En vez de modificar un diccionario, creas uno NUEVO. Cada transformación produce algo nuevo sin tocar el original.
Por qué alguien querría eso? Piensa en un pipeline de datos con 5 pasos. Si el paso 3 modifica los datos del paso 1, y luego el paso 5 necesita los datos originales... tienes un problema. La programación funcional evita esa clase de bugs: como nada se modifica, puedes encadenar transformaciones sin miedo a efectos secundarios. PySpark usa este principio al 100% — cada operación produce un DataFrame nuevo.
La buena noticia: ya has usado programación funcional sin saberlo. Las list comprehensions SON programación funcional — crean una lista nueva a partir de otra sin modificar la original. sorted() ES funcional — devuelve una lista nueva en vez de modificar la existente. La diferencia con .sort() (que modifica la lista original) es exactamente la diferencia entre estilo funcional y estilo imperativo.
1# IMPERATIVO (modifica los datos originales)2precios = [100, 250, 50, 899, 1300]3precios.sort() # modifica la lista original!4print(precios) # [50, 100, 250, 899, 1300]56# FUNCIONAL (crea datos nuevos, el original no se toca)7precios = [100, 250, 50, 899, 1300]8ordenados = sorted(precios) # crea una lista NUEVA9print(precios) # [100, 250, 50, 899, 1300] — intacto!10print(ordenados) # [50, 100, 250, 899, 1300] — copia ordenada1112# List comprehension = programacion funcional que ya conoces13precios = [100, 250, 50, 899, 1300]14con_iva = [p * 1.21 for p in precios] # lista NUEVA15caros = [p for p in precios if p > 200] # otra lista NUEVA1617print(precios) # sigue siendo [100, 250, 50, 899, 1300]18print(con_iva) # [121.0, 302.5, 60.5, 1087.79, 1573.0]19print(caros) # [250, 899, 1300]
Funcional = transformar SIN modificar el original. Cada operación produce algo nuevo.
### map, filter y lambda: las herramientas funcionales puras
Python tiene tres funciones built-in que representan la esencia de la programación funcional: map() transforma cada elemento, filter() selecciona elementos por condición, y lambda crea funciones anónimas de una línea. Las list comprehensions que ya conoces son la versión "pytónica" de map y filter — pero entender las versiones puras te prepara para PySpark, donde se usan constantemente.
1# lambda: funcion anonima de una linea2def aplicar_iva(precio):3 return precio * 1.2145# Lo mismo como lambda:6aplicar_iva_fn = lambda precio: precio * 1.217print(aplicar_iva_fn(100)) # 121.089# map(): aplica una funcion a CADA elemento10precios = [100, 250, 50, 899]11con_iva = list(map(lambda p: p * 1.21, precios))12print(con_iva) # [121.0, 302.5, 60.5, 1087.79]1314# Equivalente con list comprehension (mas pythonico):15con_iva = [p * 1.21 for p in precios]1617# filter(): selecciona elementos que cumplen una condicion18caros = list(filter(lambda p: p > 200, precios))19print(caros) # [250, 899]2021# Equivalente con list comprehension:22caros = [p for p in precios if p > 200]2324# Encadenar map + filter (estilo funcional puro):25resultado = list(map(26 lambda p: round(p * 1.21, 2),27 filter(lambda p: p > 200, precios)28))29print(resultado) # [302.5, 1087.79]
map = transformar cada elemento. filter = seleccionar por condición. lambda = función de una línea.
En Python idiomático, las list comprehensions se prefieren sobre map/filter para la mayoría de casos — son más legibles. Pero map/filter aparecen constantemente en documentación de PySpark, APIs funcionales y código legacy. Entenderlos te permite leer ESE código sin bloquearte.
Y falta el tercer miembro de la familia: reduce. Su trabajo es convertir una lista entera en un solo valor — recorriéndola con un acumulador:
1from functools import reduce23precios = [100, 250, 50]45# reduce lleva un acumulador que empieza en 06# y en cada paso lo combina con el siguiente elemento7total = reduce(lambda acum, p: acum + p, precios, 0)8print(total) # 400910# Paso a paso: 0 + 100 = 100 -> 100 + 250 = 350 -> 350 + 50 = 40011# sum(precios) hace exactamente esto; reduce sirve cuando la combinación no es una suma.
reduce convierte una lista en un valor. Es el concepto detrás de MapReduce y Spark.
### Por qué importa: Pandas usa OOP, PySpark usa funcional
Ahora viene el punto clave de toda esta lección. Las herramientas que vas a usar el resto del curso tienen una PREFERENCIA de paradigma. Pandas prefiere el estilo OOP: creas un objeto DataFrame y llamas métodos sobre él (.groupby(), .merge(), .fillna()). PySpark prefiere el estilo funcional: encadenas transformaciones que producen DataFrames nuevos sin modificar el original (.filter(), .select(), .withColumn()). Entender esta diferencia te ahorrará confusiones enormes.
1# Lo que tu ya sabes hacer HOY (Python puro)2ventas = [3 {"producto": "Laptop", "categoria": "tech", "importe": 1299},4 {"producto": "Camiseta", "categoria": "ropa", "importe": 25},5 {"producto": "Monitor", "categoria": "tech", "importe": 449},6 {"producto": "Pantalon", "categoria": "ropa", "importe": 60},7]89# Estilo funcional (filter + transform con comprehension):10tech_grandes = [11 v["producto"]12 for v in ventas13 if v["categoria"] == "tech" and v["importe"] > 20014]15print(f"Productos tech caros: {tech_grandes}") # ['Laptop', 'Monitor']1617# En Pandas seria:18# df[df["categoria"] == "tech"][df["importe"] > 200]["producto"]19#20# En PySpark (datos masivos) seria:21# df.filter((col("categoria") == "tech") & (col("importe") > 200)).select("producto")
Tú ya sabes la lógica base. Pandas usa la misma lógica con sintaxis diferente.
### sorted() con key: funciones como argumentos
Un concepto funcional que ya has visto: pasar una FUNCIÓN como argumento a otra función. Cuando haces sorted(lista, key=len), estás pasando la función len como "criterio de ordenación". No ejecutas len() — pasas LA FUNCIÓN MISMA para que sorted() la use internamente. Este patrón es la base de filter(), map(), y de cómo PySpark aplica transformaciones.
1# Funciones como argumentos (funciones de orden superior)2productos = ["laptop", "a", "monitor 4K", "teclado mecanico"]34# Ordenar por longitud del nombre5por_longitud = sorted(productos, key=len)6print(por_longitud) # ['a', 'laptop', 'monitor 4K', 'teclado mecanico']78# Ordenar registros por un campo9ventas = [10 {"producto": "Laptop", "importe": 1299},11 {"producto": "Raton", "importe": 30},12 {"producto": "Monitor", "importe": 449},13]1415por_importe = sorted(ventas, key=lambda v: v["importe"], reverse=True)16for v in por_importe:17 print(f" {v['producto']}: {v['importe']}e")1819# max/min tambien aceptan key:20mas_caro = max(ventas, key=lambda v: v["importe"])21print(f"Mas caro: {mas_caro['producto']}") # Laptop
Pasar funciones como argumentos (key=) es programación funcional en acción.
Lo que le diría a mi yo de hace 5 años: no te obsesiones con crear clases ni con dominar programación funcional "pura". Lo que necesitas es RECONOCER los patrones. Cuando veas objeto.metodo(), sabes que es OOP. Cuando veas funcion(lambda: ..., datos), sabes que es funcional. Cuando veas un for paso a paso, sabes que es imperativo. Reconocer el patrón es el 80% del valor.
### Resumen: cuándo usar cada paradigma
- IMPERATIVO (for, if, variables): scripts simples, lógica de negocio clara paso a paso, debugging. Cuando quieres que cualquiera entienda el flujo.
- OOP (objeto.metodo()): usar librerías como Pandas, manipular DataFrames, encadenar transformaciones legibles. No necesitas crear clases — solo USAR las que otros crearon.
- FUNCIONAL (map, filter, lambda, comprehensions): transformaciones de datos sin efectos secundarios. PySpark, procesamiento de streams, situaciones donde no quieres modificar los datos originales.
En la práctica diaria del trabajo con datos, mezclarás los tres estilos en el mismo script sin pensarlo: un for imperativo para recorrer archivos, una llamada OOP para leer con Pandas (pd.read_csv), y un filter funcional para limpiar valores nulos. Python te permite esa flexibilidad. Lo importante es que ahora SABES lo que estás haciendo en cada momento.
## ejercicios
Identifica el paradigma
Clasifica cada fragmento como imperativo, oop o funcional. Las comillas del hueco ya están puestas — solo sustituye el texto.
💡 Resultado esperado
Clasificacion de paradigmas: [funcional ] resultado = [x * 2 for x in datos if x > 0] [oop ] datos.sort() [funcional ] nuevo = sorted(datos, key=lambda x: x)
El mismo problema, tres estilos
Escribe tres funciones que calculen el mismo resultado: una imperativa, una funcional y una con comprehension. Las tres deben dar el mismo número.
💡 Resultado esperado
Imperativo: 1149.49 Funcional: 1149.49 Comprehension: 1149.49
Limpiar datos con métodos encadenados
Escribe la función limpiar(nombre) que normalice un nombre sucio encadenando métodos de string.
💡 Resultado esperado
Nombres limpios: Ana Garcia Luis Perez Lopez Maria Torres
Mini pipeline funcional
Escribe tres funciones — una por etapa del pipeline — que filtren, transformen y acumulen con estilo funcional puro.
💡 Resultado esperado
Clientes VIP: 3 Total VIP con IVA: 4900.50
Regístrate para guardar tu progreso.
## comentarios
Reporta erratas, ayuda a otros o comparte tu opinión. Sé constructivo.
Inicia sesión para comentar y responder.
cargando comentarios...