class: center, middle, inverse, title-slide .title[ # Introducción: OOP - Functional Programming ] .subtitle[ ## MAD - UdelaR ] .author[ ### Daniel Miles Touya ] .date[ ### 2025-06-12 ] --- <style> .scroll-output { max-height: 300px; overflow-y: auto; background: #f9f9f9; border: 1px solid #ccc; padding: 10px; font-family: monospace; font-size: 70%; white-space: pre-wrap; } </style> # Paradigmas de Programación Los paradigmas de programación son <b style="color:green"> formas de pensar y estructurar el código.</b> -- Los más comunes son: - <b style="color:green">Imperativo</b>: describe *cómo* hacer las cosas (paso a paso). - Ejemplo: Programación estructurada, OOP - <b style="color:green">Declarativo</b>: describe *qué* se quiere lograr, no cómo. - Ejemplo: Programación funcional, lógica, SQL -- Relación con OOP y FP - <b style="color:brown">Programación Orientada a Objetos (OOP)</b> ➝ Paradigma **imperativo** ➝ Se enfoca en objetos que tienen estado y comportamiento - <b style="color:brown">Programación Funcional (FP)</b> ➝ Paradigma **declarativo** ➝ Se enfoca en funciones puras y datos inmutables --- # ¿Qué es la Programación Orientada a Objetos (OOP)? La <b style="color:green">Programación Orientada a Objetos (OOP)</b> es una forma de escribir programas organizando el código en <b style="color:brown">objetos</b>. -- Un <b style="color:brown">objeto</b> es un como un _miembro de una familia_ (`class`) que comparte: - **Datos** (llamados *atributos*) - **Acciones** (llamadas *métodos*, que son funciones que pertenecen al objeto) -- Más formalmente: Un <b style="color:brown">objeto</b> es **instancia concreta** de una <b style="color:brown">clase</b>. -- Por ejemplo: - <b style="color:brown">clase</b> libro de recetas - <b style="color:brown">objeto</b> una receta paraticular - <b style="color:brown">clase</b> biblioteca - <b style="color:brown">objeto</b> un libro particular --- # Ejemplo OOP: Pizzería Imaginemos que estamos modelando una pizzería. - La <b style="color:brown">clase</b> `Pizza` representa el concepto general de una pizza. - Cada <b style="color:brown">objeto</b> es una pizza específica (por ejemplo, una Margarita o una Cuatro Quesos). - Los <b style="color:green">atributos</b> son sus ingredientes. - Los <b style="color:green">métodos</b> son acciones como `preparar()` o `agregar_ingrediente()` o `calcular_precio()` . --- # Ejemplo OOP: Pizzería ```python class Pizza: def __init__(self, nombre, ingredientes): self.nombre = nombre self.ingredientes = ingredientes def preparar(self): print(f"Preparando una pizza {self.nombre} con: {', '.join(self.ingredientes)}") def agregar_ingrediente(self, ingrediente): self.ingredientes.append(ingrediente) def calcular_precio(self): precio_por_ingrediente = 1.5 total = len(self.ingredientes) * precio_por_ingrediente return round(total, 2) ``` --- # Ejemplo OOP: Pizzería Ahora creamos una <b style="color:green">instancia</b> (un <b style="color:brown">objeto</b> real) de la clase `Pizza`: ```python margarita = Pizza("Margarita", ["tomate", "mozzarella"]) ``` -- `margarita` es una <b style="color:green">instancia</b> de la clase `Pizza`: ```python type(margarita) ``` -- - `Pizza(...)` llama al constructor de la clase, que es el método `__init__`. - "Margarita" y la lista de ingredientes se pasan como argumentos. Se crea un <b style="color:brown">objeto</b> nuevo en memoria con esos datos. - El <b style="color:brown">objeto</b> se guarda en la _variable_ <b style="color:#27aeb9">margarita</b>. --- # Ejemplo OOP: Pizzería Cuestiones: - Observar que cuando _construimos_ un <b style="color:brown">objeto</b> usando el método `__init__` usamos `self`: - `__init__(self....` - En Python, `self` es una <b style="color:green">referencia al propio objeto</b> que se está creando o usando. -- - <b style="color:#27aeb9">margarita</b> es una variable, una _etiqueta_ que apunta hacia ese <b style="color:brown">objeto</b>, e.g., la pizza. --- # Ejemplo OOP: Pizzería .pull-left[ **¿Qué es <b style="color:#27aeb9">margarita</b>?** ````python type(margarita) # O, directamente, si forma parte de la clase Pizza isinstance(margarita, Pizza) ```` ] -- .pull-right[ **Estamos preparando la pizza** ````python margarita.preparar() ```` ] -- .pull-left[ **Precio** ````python margarita.calcular_precio() ```` ] -- .pull-right[ **Otro ingrediente y precio** ````python margarita.agregar_ingrediente("albahaca") margarita.calcular_precio() ```` ] -- **Crear otra <b style="color:brown">instancia (objeto)</b> de la clase `Pizza`** ```python tropical = Pizza("Tropical", ["tomate", "jamón", "piña"]) print(margarita.calcular_precio()) margarita.agregar_ingrediente(["albahaca", "atun"]) margarita.calcular_precio() ``` --- # OOP >Ventajas - **Modularidad**: el código se divide en clases reutilizables. - **Encapsulamiento**: oculta detalles internos, mejora mantenimiento. - **Herencia**: permite crear nuevas clases basadas en otras. - **Polimorfismo**: métodos con el mismo nombre, diferentes comportamientos. -- >Cuándo usar OOP: ideal para sistemas grandes y complejos como: - Aplicaciones con interfaces gráficas - Juegos - Sistemas de gestión (ERP, CRM) --- # Python OOP - Python es un lenguaje **totalmente orientado a objetos**. - **Todo en Python es un objeto**: números, funciones, clases, módulos, listas, cadenas, etc. - Cada objeto es una **instancia de una clase**. -- <b style="color:green">¿Cuántas clases hay?</b> **El número de clases** posibles en Python **no está limitado por el lenguaje**, sino por: * Los módulos cargados. * Las clases definidas por el usuario. * Las clases generadas dinámicamente (con `type()` o metaclases). * Incluso dentro de un solo módulo puedes tener muchas clases. Por ejemplo, `matplotlib.pyplot` no define muchas clases directamente, pero `matplotlib.lines` sí define varias (`Line2D`, etc.). --- # Python OOP >Ejemplos: todo es un objeto ```python type(5) # <class 'int'> type("hola") # <class 'str'> type([1, 2, 3]) # <class 'list'> type(len) # <class 'builtin_function_or_method'> type(type) # <class 'type'> ``` --- # Python OOP <b style="color:green">¿Cuántas clases `built-in`?</b> ````python import builtins clases_builtin = [name for name in dir(builtins) if isinstance(getattr(builtins, name), type)] print(f"Total de clases built-in: {len(clases_builtin)}") print(f"A ver cuales: {clases_builtin}") ```` -- O clases definidas por un modulo ````python import collections clases_en_collections = [name for name in dir(collections) if isinstance(getattr(collections, name), type)] print(f"Total de clases: {len(clases_en_collections)}") ```` --- # Python OPP <b style="color:green">Qué significa que un objeto pertenezca a una clase?</b> Significa que el objeto es una instancia de esa clase, y por lo tanto **hereda su comportamiento y estructura**. -- Ejemplo: .pull-left[ **Objeto String** ````python texto = "Python" print(type(texto)) # <class 'str'> print(isinstance(texto, str)) # True ```` ] -- .pull-right[ **Qué hereda** ````python print(len(dir(texto))) # Verlos todo lo que hereda print(dir(texto)) ```` ] --- # Python OOP Str <div style="max-width: 100%; overflow-x: auto;"> <table border="1" cellpadding="6" cellspacing="0" style="border-collapse: collapse; width: 90%; text-align: left; font-size: 13px;"> <thead> <tr> <th>Método</th> <th>Descripción</th> <th>Ejemplo con <code>texto = "Python"</code></th> <th>Resultado</th> </tr> </thead> <tbody> <tr> <td><code>upper()</code></td> <td>Convierte a mayúsculas</td> <td><code>texto.upper()</code></td> <td><code>'PYTHON'</code></td> </tr> <tr> <td><code>lower()</code></td> <td>Convierte a minúsculas</td> <td><code>texto.lower()</code></td> <td><code>'python'</code></td> </tr> <tr> <td><code>capitalize()</code></td> <td>Primera letra en mayúscula</td> <td><code>texto.capitalize()</code></td> <td><code>'Python'</code></td> </tr> <tr> <td><code>strip()</code></td> <td>Elimina espacios iniciales/finales</td> <td><code>texto.strip()</code></td> <td><code>'Python'</code></td> </tr> <tr> <td><code>replace()</code></td> <td>Reemplaza caracteres</td> <td><code>texto.replace("P", "J")</code></td> <td><code>'Jython'</code></td> </tr> <tr> <td><code>split()</code></td> <td>Divide el string</td> <td><code>texto.split("t")</code></td> <td><code>['Py', 'hon']</code></td> </tr> <tr> <td><code>startswith()</code></td> <td>¿Empieza con...?</td> <td><code>texto.startswith("Py")</code></td> <td><code>True</code></td> </tr> <tr> <td><code>endswith()</code></td> <td>¿Termina con...?</td> <td><code>texto.endswith("on")</code></td> <td><code>True</code></td> </tr> <tr> <td><code>find()</code></td> <td>Índice de la primera aparición</td> <td><code>texto.find("t")</code></td> <td><code>2</code></td> </tr> <tr> <td><code>format()</code></td> <td>Interpolación de cadenas</td> <td><code>"Hola, {}".format(texto)</code></td> <td><code>'Hola, Python'</code></td> </tr> <tr> <td><code>len()</code></td> <td>Longitud del string</td> <td><code>len(texto)</code></td> <td><code>6</code></td> </tr> <tr> <td><code>"t" in texto</code></td> <td>¿Contiene "t"?</td> <td><code>"t" in texto</code></td> <td><code>True</code></td> </tr> </tbody> </table> </div> --- # Ejercicio OOP Asumamos que lo nombran coordinador del Master y quiere tener a todos los alumnos, con sus nombres, emails, notas, etc.. <b style="color:brown">Ejercicio: </b><b style="color:green">Crear una clase de alumnos del Master</b> -- <b style="color: #27AEB9">Ahora, que permita contar el número de alumnos que hay en el Master</b> -- <b style="color: #20AEB1">Y que permita calcular la nota media de las materias del master</b> -- <b style="color: #27AEB9">Y que permita calcular la nota media de las materias del master</b> -- <span style="color:green">Como no entiendo <b>todo</b> lo que me ha dado, le pido al _modelo de lenguaje_ que me explique, paso a paso, el script de la clase `Alumnos` que me ha reportado.</span> Antes de hacer todo esto, <a href="hablador.pdf" target = "_blank">leer la siguiente reflexion sobre el uso de modelos de lenguage.</a> --- # ¿Qué es la Programación Funcional? La **Programación Funcional (FP)** es un paradigma donde el código se organiza usando: - **Funciones puras** - **Datos inmutables**. -- **Función pura**: - Siempre devuelve el mismo resultado si se le dan los mismos datos - **No modifica** nada fuera de sí misma (no outside effects) -- **Datos inmutables** No se pueden cambiar una vez creados. --- # Ejemplo clase `Pizza` en funciones <div style="max-height: 500px; overflow-y: auto; font-size: 0.8em; border: 1px solid #ccc; padding: 10px; border-radius: 5px; background: #f9f9f9"> <pre><code class="python"> # Classe Pizza con funciones def crear_pizza(nombre, ingredientes): return {"nombre": nombre, "ingredientes": ingredientes} def preparar_pizza(p): print(f"Preparando una pizza {p['nombre']} con: {', '.join(p['ingredientes'])}") def agregar_ingrediente(pizza, ingrediente, nuevo_nombre=None): return { "nombre": nuevo_nombre if nuevo_nombre else pizza["nombre"], "ingredientes": pizza["ingredientes"] + [ingrediente] } def calcular_precio(p): return round(len(p["ingredientes"]) * 1.5, 2) # Como se usa margarita = crear_pizza("Margarita", ["queso", "tomate"]) preparar_pizza(maragarita) # Agrego pero con distinto nombre natural = agregar_ingrediente(margarita, "albahaca", "Natural") preparar_pizza(natural) precio = calcular_precio(natural) print("Precio:", precio) </code></pre> </div> --- # Ejercicio: <b style="color:green">Convertir la `clase Alumnos` en versión `funcional`, i.e, que trabaje con funciones en vez que con esturctura de clases.<b> --- # OOP: es Mutable > Clases inmutables: estilo FP Existe la posibilidad de ir a algo más `inmutable` -- Versión OOP más funcional pura, usando `dataclass(frozen=True)` para representar las pizzas como estructuras inmutables: - `frozen=True:` hace que los atributos no puedan cambiarse (inmutables). - `Tuple[str, ...]`: tuplas en lugar de listas para que los ingredientes sean inmutables. - En lugar de modificar una pizza, creamos una nueva: `nueva_pizza = agregar_ingrediente(pizza_anterior, "albahaca")` --- # Classes inmutables: estilo FP <div style="max-height: 340px; overflow-y: auto; font-size: 0.8em; background: #f9f9f9; border: 1px solid #ccc; padding: 10px; border-radius: 5px"> <pre><code class="python"> from dataclasses import dataclass from typing import Tuple @dataclass(frozen=True) class Pizza: nombre: str ingredientes: Tuple[str, ...] def crear_pizza(nombre, ingredientes): return Pizza(nombre, tuple(ingredientes)) def preparar_pizza(pizza): print(f"Preparando una pizza {pizza.nombre} con: {', '.join(pizza.ingredientes)}") def agregar_ingrediente(pizza, ingrediente, nuevo_nombre=None): return Pizza( nombre=nuevo_nombre if nuevo_nombre else pizza.nombre, ingredientes=pizza.ingredientes + (ingrediente,) ) def calcular_precio(pizza): return round(len(pizza.ingredientes) * 1.5, 2) # Uso margarita = crear_pizza("Margarita", ["queso", "tomate"]) # De que tipo es type(margarita) #¿le puedo cambiar el nombre? margarita.nombre = "marga" #Pero todo sigue siendo igual preparar_pizza(margarita) # Tento que darle un nuevo nombre natural = agregar_ingrediente(margarita, "albahaca", nuevo_nombre="Natural") preparar_pizza(natural) precio = calcular_precio(natural) print("Precio:", precio) </code></pre> </div> --- # FP: alternativa más pura El mismo script que antes, pero en más `functional view` <div style="max-height: 340px; overflow-y: auto; font-size: 0.8em; background: #f9f9f9; border: 1px solid #ccc; padding: 10px; border-radius: 5px"> <pre><code class="python"> from collections import namedtuple Pizza = namedtuple("Pizza", ["nombre", "ingredientes"]) def crear_pizza(nombre, ingredientes): return Pizza(nombre, tuple(ingredientes)) def preparar_pizza(pizza): print(f"Preparando una pizza {pizza.nombre} con: {', '.join(pizza.ingredientes)}") def agregar_ingrediente(pizza, ingrediente, nuevo_nombre=None): return Pizza( nuevo_nombre if nuevo_nombre else pizza.nombre, pizza.ingredientes + (ingrediente,) ) def calcular_precio(pizza): return round(len(pizza.ingredientes) * 1.5, 2) </code></pre> </div> --- # Ventajas de la Programación Funcional <b style="color:green">Código más predecible</b> - Las funciones puras siempre devuelven el mismo resultado. - No hay efectos secundarios ocultos. <b style="color:green">Menos errores</b> - Al no modificar datos, se evitan errores por cambios inesperados. - Ideal para trabajar en equipo o en sistemas grandes. <b style="color:green">Más fácil de testear</b> - Las funciones se pueden probar de forma aislada. - No dependen del estado del programa. --- # Ventajas de la Programación Funcional <b style="color:green">Ideal para procesamiento de datos</b> - Se pueden aplicar funciones como `map`, `filter`, `reduce` para transformar datos de forma clara y concisa. <b style="color:green">Facilita la concurrencia y el paralelismo</b> - Al no haber estado compartido, es más seguro ejecutar funciones en paralelo. <b style="color:green">Composición de funciones</b> - Se pueden construir funciones complejas combinando funciones simples. - Esto favorece la reutilización y la claridad del código. --- # Conclusión ¿Qué vimos? - Paradigmas de Programación: - OOP - FP - Python es un lenguage OOP que permite FP - Conceptos a profundizar - función: scope, closures... - mutabilidad, inmutabilidad - estructura de datos - modulos - class, atributo, método... - ... --- # Ejercicio: Random Forest **Objetivo**: Comprensión Lectora **Random Forest** 1.- ¿Qué es un método **random forest**? 2.- ¿Para qué se utiliza? A continuación se presentan dos aproximaciones a la programación de RF: una mediante OOP y la otra mediante funciones. Objetivo: Comprender Forma de Trabajo a.- Tener, primero, la _pregunta_ clara, e.g., queremos aplicar un RF. b.- Desestructurar para comprender que hace el script: detenerse en cada concepto que no se entiende y preguntarle a un modelo de lenguage. --- # Ejercicio: Random Forest **Objetivo**: Visualizar la programación de un RF **OOP** <div style="max-height: 340px; overflow-y: auto; font-size: 0.8em; background: #f9f9f9; border: 1px solid #ccc; padding: 10px; border-radius: 5px"> <pre><code class="python"> import numpy as np from sklearn.tree import DecisionTreeClassifier from sklearn.datasets import make_classification from collections import Counter class SimpleRandomForest: def __init__(self, n_trees=10, max_depth=None, sample_size=0.8): self.n_trees = n_trees self.max_depth = max_depth self.sample_size = sample_size self.trees = [] def _bootstrap_sample(self, X, y): n_samples = int(self.sample_size * X.shape[0]) indices = np.random.choice(X.shape[0], n_samples, replace=True) return X[indices], y[indices] def fit(self, X, y): self.trees = [] for _ in range(self.n_trees): X_sample, y_sample = self._bootstrap_sample(X, y) tree = DecisionTreeClassifier(max_depth=self.max_depth) tree.fit(X_sample, y_sample) self.trees.append(tree) def predict(self, X): tree_preds = np.array([tree.predict(X) for tree in self.trees]) # votación por mayoría return np.apply_along_axis(lambda x: Counter(x).most_common(1)[0][0], axis=0, arr=tree_preds) </code></pre> </div> --- # Ejercicio: Random Forest Ejecución <div style="max-height: 340px; overflow-y: auto; font-size: 0.8em; background: #f9f9f9; border: 1px solid #ccc; padding: 10px; border-radius: 5px"> <pre><code class="python"> from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score # Simula un dataset de clasificación X, y = make_classification(n_samples=1000, n_features=10, n_informative=5, n_classes=2, random_state=42) # Divide en entrenamiento y prueba X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1) # Crea y entrena el Random Forest forest = SimpleRandomForest(n_trees=20, max_depth=5) forest.fit(X_train, y_train) # Predice y evalúa y_pred = forest.predict(X_test) print("Accuracy:", accuracy_score(y_test, y_pred)) </code></pre> </div> --- # Ejercicio: Random Forest Cramos distintas instancias <div style="max-height: 340px; overflow-y: auto; font-size: 0.8em; background: #f9f9f9; border: 1px solid #ccc; padding: 10px; border-radius: 5px"> <pre><code class="python"> # Creamos y entrenamos Random Forest forest1 = SimpleRandomForest(n_trees=10, max_depth=3) forest1.fit(X_train, y_train) pred1 = forest1.predict(X_test) acc1 = accuracy_score(y_test, pred1) print("Forest 1 - Árboles: 10, max_depth: 3 → Accuracy:", acc1) # Creamos y entrenamos el otroRandom Forest (más profundo, más árboles) forest2 = SimpleRandomForest(n_trees=50, max_depth=10) forest2.fit(X_train, y_train) pred2 = forest2.predict(X_test) acc2 = accuracy_score(y_test, pred2) print("Forest 2 - Árboles: 50, max_depth: 10 → Accuracy:", acc2) </code></pre> </div> --- # Ejercicio: Random Forest **Objetivo**: Visualizar la programación de un RF **Functional P** <div style="max-height: 340px; overflow-y: auto; font-size: 0.8em; background: #f9f9f9; border: 1px solid #ccc; padding: 10px; border-radius: 5px"> <pre><code class="python"> import numpy as np from sklearn.tree import DecisionTreeClassifier from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score from collections import Counter # Crear muestras bootstrap def bootstrap_sample(X, y, sample_size): n_samples = int(len(X) * sample_size) indices = np.random.choice(len(X), n_samples, replace=True) return X[indices], y[indices] # Entrenar una lista de árboles def entrenar_forest(X, y, n_trees=10, max_depth=None, sample_size=0.8): return [ DecisionTreeClassifier(max_depth=max_depth).fit(*bootstrap_sample(X, y, sample_size)) for _ in range(n_trees) ] # Hacer predicciones usando votación por mayoría def predecir_forest(forest, X): predicciones = np.array([tree.predict(X) for tree in forest]) return np.apply_along_axis(lambda col: Counter(col).most_common(1)[0][0], axis=0, arr=predicciones) </code></pre> </div> --- # Ejercicio: Random Forest **Objetivo**: Visualizar la programación de un RF **Functional Programming** Ejecución <div style="max-height: 340px; overflow-y: auto; font-size: 0.8em; background: #f9f9f9; border: 1px solid #ccc; padding: 10px; border-radius: 5px"> <pre><code class="python"> # Simular datos X, y = make_classification(n_samples=1000, n_features=10, n_informative=5, random_state=42) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1) # Entrenar primer bosque (simple) forest1 = entrenar_forest(X_train, y_train, n_trees=10, max_depth=3) pred1 = predecir_forest(forest1, X_test) acc1 = accuracy_score(y_test, pred1) print("Bosque 1 → Árboles: 10, max_depth: 3 → Accuracy:", acc1) # Entrenar segundo bosque (complejo) forest2 = entrenar_forest(X_train, y_train, n_trees=50, max_depth=10) pred2 = predecir_forest(forest2, X_test) acc2 = accuracy_score(y_test, pred2) print("Bosque 2 → Árboles: 50, max_depth: 10 → Accuracy:", acc2) </code></pre> </div> --- # Ejercicio Aquí adjunto un artículo de Irene Vallejo: <a href="https://www.pensamientocritico.org/wp-content/uploads/Vallejo-abr-2025.pdf" target="_blank">_Lo que sabemos sobre la ignorancia_ </a> que quiero que lean y mediten) <a href="https://www.pensamientocritico.org/wp-content/uploads/Vallejo-abr-2025.pdf" target="_blank"> <img alt="Ilustración por Fernando Vicente de la tribuna 'Lo que sabemos de la ignorancia', de Irene Vallejo, 9 de marzo de 2025." decoding="auto" class="_re a_m-h" height="409" srcset="https://imagenes.elpais.com/resizer/v2/UG5LLNLXRNAV7E2LLPCUFMOQNA.jpg?auth=edd1f5409759e65643759466ca476846de2215995658ddcadce9318b5649a2ab&width=414 414w,https://imagenes.elpais.com/resizer/v2/UG5LLNLXRNAV7E2LLPCUFMOQNA.jpg?auth=edd1f5409759e65643759466ca476846de2215995658ddcadce9318b5649a2ab&width=828 640w,https://imagenes.elpais.com/resizer/v2/UG5LLNLXRNAV7E2LLPCUFMOQNA.jpg?auth=edd1f5409759e65643759466ca476846de2215995658ddcadce9318b5649a2ab&width=980 1000w,https://imagenes.elpais.com/resizer/v2/UG5LLNLXRNAV7E2LLPCUFMOQNA.jpg?auth=edd1f5409759e65643759466ca476846de2215995658ddcadce9318b5649a2ab&width=1200 1200w" width="414" sizes="(min-width:1199px) 661px, (min-width:1001px) 54vw, (min-width:768px) 715px, 100vw" src="https://imagenes.elpais.com/resizer/v2/UG5LLNLXRNAV7E2LLPCUFMOQNA.jpg?auth=edd1f5409759e65643759466ca476846de2215995658ddcadce9318b5649a2ab&width=414" loading="eager"> </a>