Paradigmas de Programación · Unidad 1
Programación funcional
Qué es un paradigma, cuáles se usan hoy y cómo se aplica el paradigma funcional en Python: funciones como objetos, callbacks, lambdas y los operadores map, filter y reduce.
Punto de partida
Introducción y objetivos
La unidad repasa los paradigmas de programación que se usan hoy y las necesidades que cubre cada uno. Muchos ya los usamos sin haberles puesto nombre; saber diferenciarlos es clave para desarrollar y mantener software.
La asignatura es totalmente práctica y se centra en Python, un lenguaje muy versátil para explorar distintos paradigmas. Se trabaja con Visual Studio Code, entornos virtuales y ficheros .py e .ipynb. Aunque Python no es el lenguaje que mejor encaja con el paradigma funcional, permite implementar callbacks o wrappers.
Objetivos de la unidad (4)
- Comprender qué es un paradigma de programación, su importancia y cómo la elección de uno afecta a la claridad, la eficiencia y el mantenimiento del código.
- Conocer los principales paradigmas actuales: características, ámbitos de aplicación, ventajas, desventajas y lenguajes representativos.
- Iniciarse en Python y los entornos virtuales: instalar y configurar Python con buenas prácticas y crear y gestionar entornos para aislar dependencias.
- Entender en profundidad el paradigma funcional en Python: sus construcciones, su combinación con otros paradigmas y cuándo conviene usarlo para mejorar legibilidad y mantenibilidad.
Bloque 1
Introducción a los paradigmas de programación
Concepto de paradigma de programación
Según la RAE, un paradigma es:
- Ejemplo o ejemplar.
- Teoría o conjunto de teorías cuyo núcleo central se acepta sin cuestionar y que suministra la base y modelo para resolver problemas y avanzar en el conocimiento (p. ej., el paradigma newtoniano). Es la acepción más próxima al software.
- Relación de elementos que comparten un mismo contexto fonológico, morfológico o sintáctico.
- Esquema formal en el que se organizan las palabras que admiten modificaciones flexivas o derivativas.
Ninguna habla de software, pero todas comparten la idea de un conjunto de entidades con una base común que sirve como modelo de clasificación.
Un paradigma de programación es un modelo que define un estilo fundamental de programación y sirve de base para avanzar en el desarrollo de software.
El concepto surge en 1978 para clasificar los estilos de programación, ofreciendo conceptos y abstracciones (funciones, variables, objetos, flujos de datos…) que permiten abstraerse de la arquitectura básica del sistema.
- Un lenguaje no tiene por qué cumplir un solo paradigma: la mayoría de lenguajes modernos son multiparadigma.
- Todos los paradigmas buscan la modularidad para mejorar eficiencia y costes de mantenimiento. La modularización también limita el alcance del software en el equipo donde se ejecuta, lo que aporta seguridad.
- Hoy se dividen en dos grandes bloques: imperativo y declarativo.
Programación imperativa
El paradigma más antiguo. Viene de imperare (ordenar): consiste en dar órdenes concretas para que el equipo las procese y ejecute.
- Se define paso a paso cómo obtener el resultado con instrucciones bien definidas.
- Las sentencias cambian el estado del programa (modifican sus datos).
- Se ejecutan de forma secuencial, lo que da un claro determinismo en la salida.
Los pasos están bien definidos y siguiéndolos uno a uno se llega al resultado. Cada paso modifica la tarta (el estado) y la receta se sigue en orden.
Suelen ser lenguajes cercanos al sistema: obligan a gestionar memoria, datos a nivel de bit o incluso registros de CPU, lo que supone muchas líneas de código para tareas que en otros paradigmas son mucho más cortas.
- C/C++
- Java
- Pascal
- Cobol
- FORTRAN
- C#
- Python
Dentro del imperativo están la programación estructurada, la procedimental y la orientada a objetos.
Programación estructurada
Busca mejorar la claridad, la calidad y el tiempo de desarrollo usando únicamente subrutinas y funciones. Surge en 1966 con el teorema de la programación estructurada.
Antes era habitual la instrucción GoTo (similar a JUMP en ensamblador MIPS): se define una etiqueta o línea y el programa salta a ese punto. Parece útil para reutilizar código, pero es muy poco eficiente en mantenibilidad y legibilidad (el famoso spaghetti code).
El material original incluye un ejemplo en Fortran que calcula π usando GoTo; la imagen no está disponible en la exportación.
Programación procedimental
Deriva directamente de la estructurada y se basa en la llamada a procedimientos. Un procedimiento (coloquialmente, «función») es una subrutina con una serie de pasos que puede llamarse en cualquier momento: más reutilización, menos tiempo de desarrollo y programas más cortos y legibles.
Es tan relevante que los procesadores añaden soporte hardware: registro de pila e instrucciones de llamada que guardan el punto de retorno casi sin penalización.
Variables locales
Solo existen en el contexto de un procedimiento: se crean dentro y se destruyen al terminar.
Variables globales
No están ligadas a ningún procedimiento: pueden crearse, modificarse y eliminarse en cualquier punto.
Programación orientada a objetos
Organiza el software en torno a datos u objetos en lugar de funciones y lógica. Un objeto es un campo de datos con atributos y comportamientos propios. Encaja con programas grandes, complejos y en mantenimiento activo, y favorece el trabajo en equipo gracias a su alta modularización.
Encapsulación
La implementación queda dentro del objeto; los demás solo usan sus métodos públicos (seguridad).
Abstracción
Solo se muestran los mecanismos internos relevantes para manipular el objeto.
Herencia
Reutilización de código entre clases.
Polimorfismo
Un objeto puede adoptar varias «formas» según el contexto; el programa decide su uso en cada ejecución.
Programación declarativa
Al contrario que la imperativa, describe qué solución se quiere (condiciones, proposiciones, afirmaciones o transformaciones) sin indicar los pasos para obtenerla, lo que ayuda a evitar efectos secundarios. Normalmente se considera declarativo todo lo que no es imperativo.
En el imperativo se define todo el procedimiento a más bajo nivel, pudiendo afectar a los datos. En el declarativo se describe el resultado y es el lenguaje, framework o librería quien lo produce.
Ejemplo ilustrativo (el material lo muestra en JavaScript con imágenes no disponibles; aquí en Python): obtener los cuadrados de los números pares.
numeros = [1, 2, 3, 4, 5, 6]
cuadrados_pares = []
for n in numeros: # cómo: recorrer...
if n % 2 == 0: # ...comprobar...
cuadrados_pares.append(n * n) # ...y modificar el estado
print(cuadrados_pares) # [4, 16, 36]
numeros = [1, 2, 3, 4, 5, 6]
cuadrados_pares = [n * n for n in numeros if n % 2 == 0] # qué quiero
print(cuadrados_pares) # [4, 16, 36]
No son excluyentes: a menudo se escriben procedimientos imperativos para luego usarlos de forma declarativa.
Características del modelo
- Alto nivel de abstracción: programas complejos expresados de forma compacta.
- Optimización sin tocar el algoritmo: basta con sustituir los procedimientos a los que se accede (p. ej., un método de ordenación más rápido).
- Desarrollo muy rápido: ideal para prototipado en metodologías ágiles.
Lenguajes únicamente declarativos:
- HTML/CSS
- SQL
- Haskell
- Prolog
Solo describen el resultado; una implementación ajena hace el trabajo. En una consulta SQL se indica qué obtener o insertar y el motor se encarga del proceso; en HTML se describen elementos y aspecto, no el pintado de píxeles.
SELECT nombre FROM alumnos WHERE nota >= 5; -- qué datos quiero, no cómo buscarlos
Programación funcional
Deriva de la procedimental y trabaja directamente con funciones, tratándolas como cualquier otra entidad del lenguaje. Se desarrolla en el bloque 3.
Programación reactiva
Trabaja con flujos de datos asíncronos: los datos se propagan y los módulos «reaccionan» ejecutando eventos al recibirlos.
En un enfoque imperativo, la CPU comprueba continuamente si han llegado datos (polling):
while(!exit){
if(checkDataReceive()){
receiveData();
}
}
La reactiva se basa en el patrón Observer: los nuevos datos se notifican directamente a los interesados en lugar de que estos pregunten, invirtiendo el diseño tradicional de entrada/salida. La CPU solo trabaja cuando un módulo recibe una notificación.
Los sistemas reactivos son:
Responsivos
Cumplen tiempos de respuesta establecidos; los problemas de rendimiento se detectan fácilmente.
Resilientes
Siguen respondiendo incluso ante errores.
Elásticos
Siguen respondiendo ante aumentos de carga.
Orientados a mensajes
Bajo acoplamiento mediante intercambio asíncrono de mensajes.
Programación multiparadigma
La mayoría de lenguajes modernos implementan más de un paradigma, lo que permite elegir o combinar según el proyecto. La elección depende menos del lenguaje y más de las decisiones de diseño de la arquitectura.
Comparativa de paradigmas
Imperativo
Secuencia de instrucciones que modifican el estado.
- Ventajas
- Control detallado, ejecución determinista, fácil de seguir en problemas simples.
- Desventajas
- Más errores por efectos secundarios; poco expresivo en tareas de alto nivel.
- Casos de uso
- Programas de sistemas, videojuegos, software muy ligado al hardware.
Estructurado
Subparadigma del imperativo: secuencias, condicionales y bucles en lugar de saltos incontrolados.
- Ventajas
- Código más claro y mantenible; evita el spaghetti code.
- Desventajas
- Menos flexible en tareas muy específicas de bajo nivel.
- Casos de uso
- Software de propósito general, algoritmos educativos.
Procedimental
Subparadigma del estructurado: divide el programa en procedimientos reutilizables.
- Ventajas
- Reutilización, claridad y modularidad.
- Desventajas
- Dependencia de variables globales si no se estructura bien.
- Casos de uso
- Aplicaciones científicas, scripts, herramientas de línea de comandos.
Orientado a objetos
Software organizado en objetos con datos y comportamientos.
- Ventajas
- Modularidad, herencia, encapsulación, polimorfismo.
- Desventajas
- Curva de aprendizaje; riesgo de aumentar la complejidad.
- Casos de uso
- Aplicaciones empresariales, videojuegos, sistemas complejos y colaborativos.
Declarativo
Describe el qué sin indicar el cómo.
- Ventajas
- Más abstracción, menos código, fácil cambiar la implementación.
- Desventajas
- Menos control del flujo; depende de optimizaciones internas.
- Casos de uso
- Consultas a bases de datos, interfaces web, reglas de negocio.
Funcional
Funciones puras, inmutabilidad y composición de funciones.
- Ventajas
- Código predecible, fácil de testear, ideal para paralelismo.
- Desventajas
- Curva de aprendizaje inicial; a veces menos eficiente en memoria.
- Casos de uso
- Procesamiento de datos, algoritmos matemáticos, concurrencia.
Reactivo
Procesamiento asíncrono de flujos de datos y eventos; patrón Observer.
- Ventajas
- Escalabilidad, tiempo real, bajo acoplamiento, buen manejo de E/S y concurrencia.
- Desventajas
- Complejidad conceptual (asincronía, errores), depuración difícil.
- Casos de uso
- Interfaces reactivas, streaming, sistemas event-driven, IoT, notificaciones.
Multiparadigma
Combina características de varios paradigmas.
- Ventajas
- Flexibilidad y adaptabilidad; aprovecha lo mejor de cada enfoque.
- Desventajas
- Mezcla incoherente de estilos si no hay disciplina.
- Casos de uso
- Aplicaciones complejas con enfoques distintos en cada capa.
Paradigmas por lenguaje
Indica qué paradigmas puede implementar cada lenguaje: C, Java, C++, Python, JavaScript, SQL, CSS y HTML.
Ver solución del material
| Lenguaje | Imper. | Estruct. | Proced. | POO | Funcional | Declar. | Reactivo | Multi |
|---|---|---|---|---|---|---|---|---|
| C | ✓ sí | ✓ sí | ✓ sí | |||||
| Java | ✓ sí | ✓ sí | ~ limitado | ✓ sí | ||||
| C++ | ✓ sí | ✓ sí | ~ limitado | ✓ sí | ||||
| Python | ✓ sí | ✓ sí | ✓ sí | ✓ sí | ✓ sí | ~ parcial | ✓ sí | ✓ sí |
| JavaScript | ✓ sí | ✓ sí, con prototipos | ✓ sí | ~ parcial | ~ con librerías | ✓ sí | ||
| SQL | ✓ sí | |||||||
| CSS | ✓ sí | |||||||
| HTML | ✓ sí |
En JavaScript la orientación a objetos se basa en prototipos y la reactividad se consigue con librerías.
Aplicaciones prácticas y tendencias futuras
Procesamiento de datos
El funcional transforma y filtra grandes volúmenes de forma concisa y paralelizable.
Desarrollo web
El declarativo es clave en HTML, CSS y en frameworks reactivos que describen el estado de la UI.
Automatización
El imperativo es práctico cuando hace falta controlar cada paso.
Aplicaciones híbridas
En Python: POO para la estructura, imperativo para el flujo y funcional para las colecciones.
Ejemplo: sistema de comercio electrónico
| Paradigma | Uso en la tienda |
|---|---|
| Declarativo | Definir la interfaz y las consultas a la base de datos. |
| Imperativo | Gestionar la lógica de negocio paso a paso. |
| Funcional | Filtrar y transformar datos de productos y pedidos. |
Tendencias futuras
Crecen los lenguajes y entornos multiparadigma, porque la complejidad actual exige soluciones flexibles:
- Más programación funcional en lenguajes imperativos (Java, Python, C#), impulsada por el paralelismo y la reactividad.
- Orientación a datos y flujos: arquitecturas reactivas y sistemas distribuidos favorecen lo declarativo y lo funcional.
- Herramientas inteligentes y generación de código: paradigmas que se adaptan al problema, combinando instrucciones imperativas con descripciones declarativas optimizadas por IA.
- Mantenibilidad y escalabilidad: la modularidad y la reducción de efectos secundarios serán cada vez más valoradas.
Bloque 2
Preparación del entorno de trabajo
Python como lenguaje interpretado
Python es multiparadigma e interpretado: no se compila a código máquina, sino que un programa instalado en el equipo (el intérprete) lee las instrucciones y las traduce en tiempo real.
Hay muchas versiones de Python y de sus librerías y cada proyecto arrastra muchas dependencias, así que hace falta compartimentar los paquetes: los entornos virtuales.
Instalación del entorno de trabajo
- Instala el intérprete de Python desde su web o repositorio oficial. Vale cualquier versión ≥ 3.9. Durante la instalación, incluye las librerías tcl/tk.
- Instala Visual Studio Code con las extensiones Python y Jupyter.
- En Windows 10/11, permite la ejecución de scripts de PowerShell (vienen bloqueados si no están firmados). Abre PowerShell como administrador:
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser
Recomendación (no incluida en el material): RemoteSigned también permite ejecutar Activate.ps1 y es más restrictiva, porque sigue bloqueando los scripts descargados de internet sin firmar.
Se asume Windows, pero puede usarse cualquier sistema operativo.
Entornos virtuales
Tener muchos paquetes en el mismo intérprete genera conflictos de versiones.
Un programa necesita requests 1.2.3 y otro requests ≥ 2.0.0. Solo puede haber una versión instalada, así que uno de los dos no funcionará. Y un proyecto real tiene cientos de dependencias.
Un entorno virtual es un entorno aislado por proyecto: sus dependencias no interfieren con las de otros. La herramienta habitual es venv; la mayoría de gestores (Anaconda, Miniforge) dependen de ella.
Creación
python -m venv nombre_del_entorno
Crea un directorio con ese nombre que contiene una copia aislada de la instalación de Python.
Activación
nombre_del_entorno\Scripts\activate
nombre_del_entorno\Scripts\Activate.ps1
source nombre_del_entorno/bin/activate
Ejemplo completo
Crear y activar un entorno llamado venv e instalar requests:
python -m venv venv
source venv/Scripts/Activate.ps1
pip install requests
source es de bash y Activate.ps1 es de PowerShell, así que esa combinación no funciona tal cual. Usa la de tu terminal: venv\Scripts\Activate.ps1 en PowerShell, venv\Scripts\activate en CMD o source venv/Scripts/activate en Git Bash.
El paquete se instala solo en el entorno virtual, no en la instalación global. Al ejecutar un script en VS Code, este te preguntará qué entorno usar.
Bloque 3
Programación funcional. Uso de callbacks
El paradigma funcional resuelve grandes problemas del enfoque imperativo: en lugar de describir cómo resolver una tarea paso a paso, se centra en qué resultado se quiere, apoyándose en funciones puras e inmutabilidad. Python no es funcional en su núcleo, pero permite aplicar muchos de sus principios.
Paradigma de programación funcional
Se basa en la evaluación de funciones puras y la inmutabilidad de los datos, inspirándose en la matemática funcional.
Función pura
Mismos argumentos → mismo resultado, que depende solo de las entradas. Sin efectos secundarios: no modifica variables globales, no escribe en disco…
Inmutabilidad
Los datos no se modifican: si algo cambia, se crea una estructura nueva. Simplifica la concurrencia y la depuración.
Ejemplo ilustrativo (no incluido en el material):
contador = 0
def impura(x):
global contador
contador += 1 # efecto secundario: modifica estado externo
return x + contador # el resultado depende de algo más que x
def pura(x):
return x + 1 # mismo x → mismo resultado, siempre
Como todo consiste en evaluar funciones, hay mucha recursividad y llamadas anidadas, sin modificar datos como en el imperativo.
Ventajas
- Alto nivel: se describe el resultado, no los pasos. Sentencias simples con mucha carga computacional.
- Transparencia: una función pura solo depende de sus entradas; sin efectos secundarios, depurar y mantener es más fácil.
- Paralelización: sin efectos secundarios ni dependencias externas, las rutinas se ejecutan concurrentemente con facilidad.
Casi todos los lenguajes admiten algo de programación funcional; en otros prácticamente todo el código es funcional (Haskell, Clojure…).
Programación funcional en Python
Para soportar el paradigma, las funciones deben poder:
- Recibir una función como argumento.
- Devolver una función a quien la llamó.
En Python todo es un objeto, incluidas las funciones (un objeto con un atributo que indica que puede invocarse). Por eso se tratan como cualquier otra variable:
def func():
print("I am function func()!")
func()
another_name = func # alias: sin paréntesis, no se ejecuta
another_name()
print(another_name)
I am function func()!
I am function func()!
<function func at 0x0000019425632CA0>
El alias sigue referenciando a la función original func.
print("cat", func, 42) # se imprime como cualquier valor
objects = ["cat", func, 42]
myfunc = objects[1] # se accede como a cualquier elemento
myfunc()
objects[1]() # o se invoca directamente
d = {"cat": 1, func: 2, 42: 3} # la función como clave
print(d[func]) # 2
Callbacks
Un callback es una función que se pasa como argumento a otra para que esta la invoque en un momento dado (ante un evento, al terminar una operación o para delegar parte de su lógica). Conserva el contexto de su definición (clausura) y su ejecución queda bajo el control de la función receptora. También se habla de composición de funciones.
def inner():
print("I am function inner()!")
def outer(function):
function()
outer(inner)
Al pasar inner no se ejecuta (va sin ()). Solo se ejecuta dentro de outer, en function().
Ejemplo: sorted y el argumento key
sorted devuelve una nueva lista ordenada:
animals = ["ferret", "vole", "dog", "gecko"]
print(sorted(animals))
['dog', 'ferret', 'gecko', 'vole']
Parece alfabético, pero compara los valores ASCII de los caracteres. Si una palabra empieza en mayúscula, su valor es menor que el de cualquier minúscula y el orden deja de ser el esperado.
El argumento key recibe una función (un callback) que se aplica a cada elemento; su resultado es el criterio de orden:
print(sorted(animals, key=len))
print(sorted(animals, key=len, reverse=True)) # descendente
['dog', 'vole', 'gecko', 'ferret']
['ferret', 'gecko', 'vole', 'dog']
Orden descendente por longitud sin reverse
Implementa una función para pasar como key a sorted que ordene la lista de mayor a menor longitud sin usar reverse.
animals = ["ferret", "vole", "dog", "gecko"]
sorted_animals = sorted(animals, key=...) # completar
print(sorted_animals)
Ver solución del material
def reverse_len(s):
return -len(s)
animals = ["ferret", "vole", "dog", "gecko"]
print(sorted(animals, key=reverse_len))
['ferret', 'gecko', 'vole', 'dog']
Al negar la longitud, las palabras más largas tienen la clave más pequeña y quedan primero.
Devolver una función
def outer():
def inner():
print("I am function inner()!")
# Function outer() returns function inner()
return inner
function = outer()
function # referencia a inner, no la ejecuta
function()
outer()()
- Se pueden definir funciones dentro de otras:
innersolo existe en el contexto deouter. - Al devolver
innery guardarla enfunction, sigue siendo accesible. outer()(): el primer()ejecutaouter, que devuelveinner; el segundo ejecutainner.
Pasar una función vs. pasar su resultado
No es lo mismo. En C, printText(helloText()) no es programación funcional: primero se ejecuta helloText y lo que se pasa es su resultado.
void printText(char* text){
printf("%s\n", text);
}
char* helloText(){
return "Hello World!";
}
int main(){
printText(helloText());
return 0;
}
int main(){
char* mText = helloText(); // se ejecuta primero...
printText(mText); // ...y se pasa el resultado
return 0;
}
Edad de mi mascota
Implementa edad_de_mi_mascota, que devuelva la edad de una mascota en años humanos. Recibe un entero con la edad y una función conversora; crea una conversora por animal:
- Perro: 1 año = 7 años humanos
- Gato: 1 año = 5
- Pez: 1 año = 12
Ver solución propuesta · no incluida en el material
def perro(edad):
return edad * 7
def gato(edad):
return edad * 5
def pez(edad):
return edad * 12
def edad_de_mi_mascota(edad, conversor):
return conversor(edad) # conversor es el callback
print(edad_de_mi_mascota(3, perro))
print(edad_de_mi_mascota(3, gato))
print(edad_de_mi_mascota(3, pez))
21
15
36
Ejercicio guiado: callbacks con requests y la API de OMDB
Objetivo: buscar películas con la API de OMDB (omdbapi.com), una API gratuita con datos de IMDB accesible por HTTP.
- En la web, entra en la sección API Key.
- Regístrate (puedes usar el correo de U-tad) indicando uso educativo.
- Activa la clave desde el email recibido.
Llamada de prueba (el parámetro s indica búsqueda). Devuelve un JSON con todos los resultados:
http://www.omdbapi.com/?apikey=<API_KEY>&s=interstellar
Uso de requests, con el entorno virtual activado:
pip install requests
from requests import get
response = get("http://www.omdbapi.com/?apikey=API_KEY&s=interstellar")
print(response.text)
if response.status_code == 200:
...
else:
...
El material escribe response.status_code is 200. Para comparar números usa ==: is compara la identidad de los objetos y puede dar resultados inesperados (Python 3.8+ incluso muestra un SyntaxWarning).
Para leer los datos como diccionario:
import json
data_dict = json.loads(response.content)
print(data_dict["Search"][0])
# o directamente con el método de la respuesta
print(response.json()["Search"][0])
Buscador de películas con callbacks
Implementa get_movie_data: pide un título por teclado y consulta OMDB. Si todo va bien, muestra los resultados (Título – Año: URL_del_póster); si hay error, muestra el mensaje.
Condición: get_movie_data no trata los datos ni muestra resultados o errores. Tras la petición, delega en un callback.
Así la función queda desacoplada y sirve para cualquier GET, por lo que conviene renombrarla:
def make_get_request(url, on_success, on_error):
...
Ampliación: tras obtener la información, descarga el póster. get solo puede llamarse desde make_get_request, que se invoca dos veces: una con success_get_movie_func (obtiene la URL del póster) y otra con success_get_poster_func (crea una Image a partir de los bytes y la guarda). Para ello instala Pillow:
pip install Pillow
from PIL import Image
from io import BytesIO
img = Image.open(BytesIO(image_bytes))
Ver solución propuesta · no incluida en el material
import json
from io import BytesIO
from urllib.parse import quote
from PIL import Image
from requests import get
API_KEY = "TU_API_KEY"
def make_get_request(url, on_success, on_error):
"""Hace la petición GET y delega el resultado en los callbacks."""
response = get(url)
if response.status_code == 200:
on_success(response.content)
else:
on_error(response.status_code)
def show_error(status_code):
print(f"Error en la petición (HTTP {status_code})")
def success_get_poster_func(data):
img = Image.open(BytesIO(data))
img.save("poster.png")
print("Póster guardado en poster.png")
def success_get_movie_func(data):
movies = json.loads(data)
# OMDB responde 200 aunque no encuentre nada: lo indica en "Response"
if movies.get("Response") != "True":
print("Error:", movies.get("Error"))
return
for movie in movies["Search"]:
print(f'{movie["Title"]} – {movie["Year"]}: {movie["Poster"]}')
poster_url = movies["Search"][0]["Poster"]
if poster_url != "N/A":
make_get_request(poster_url, success_get_poster_func, show_error)
if __name__ == "__main__":
title = input("Título: ")
url = f"http://www.omdbapi.com/?apikey={API_KEY}&s={quote(title)}"
make_get_request(url, success_get_movie_func, show_error)
Detalles importantes:
getsolo aparece enmake_get_request, que se reutiliza para el JSON y para la imagen.- OMDB devuelve 200 aunque no encuentre la película (
"Response": "False"), así que ese caso se trata en el callback de éxito. quotecodifica el título para la URL (espacios, tildes…).
Funciones anónimas (lambda)
El estilo funcional usa muchas funciones cortas que a veces se llaman una sola vez. Para eso existen las funciones lambda: anónimas y definidas sobre la marcha, sin def.
lambda <parameter_list>: <expression>
lambda s: s[::-1] # invierte una cadena
callable(lambda s: s[::-1]) # True: también es un objeto invocable
reverse = lambda s: s[::-1] # con alias...
reverse("I am a string")
def reverse(s): # ...equivale a
return s[::-1]
(lambda s: s[::-1])("I am a string") # o se usa directamente
'gnirts a ma I'
Pueden recibir varios parámetros:
(lambda x1, x2, x3: (x1 + x2 + x3) / 3)(9, 6, 6) # 7.0
(lambda x1, x2, x3: (x1 + x2 + x3) / 3)(1.4, 1.1, 0.5) # 1.0
Una lambda solo admite una única expresión, así que no sirve para lógica compleja como la del ejercicio de OMDB.
Operador map()
Función integrada que aplica una función a cada elemento de un iterable y devuelve un iterador con los resultados. Sustituye a un bucle for o while explícito.
map(<f>, <iterable>)
def reverse(s):
return s[::-1]
animals = ["cat", "dog", "hedgehog", "gecko"]
iterator = map(reverse, animals)
print(iterator)
<map object at 0x...>
map no devuelve una lista, sino un iterador. Para obtener los valores:
for i in map(reverse, animals):
print(i)
print(list(map(reverse, animals)))
# ['tac', 'god', 'gohegdeh', 'okceg']
list(map(lambda s: s[::-1], ["cat", "dog", "hedgehog", "gecko"]))
Un iterador solo se puede recorrer una vez: si lo conviertes en lista, después estará vacío.
Ejemplo práctico con str.join
str.join concatena una lista de cadenas intercalando el separador, pero falla con números:
print("+".join(["cat", "dog", "hedgehog", "gecko"])) # cat+dog+hedgehog+gecko
print("+".join([1, 2, 3, 4, 5]))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sequence item 0: expected str instance, int found
Solución: convertir cada elemento a texto con map:
print("+".join(map(str, [1, 2, 3, 4, 5]))) # 1+2+3+4+5
map con varios iterables
map(<f>, <iterable₁>, <iterable₂>, ..., <iterableₙ>)
La función se aplica «en columnas», tomando un elemento de cada iterable. Debe tener tantos parámetros como iterables.
| Posición | A | B | C | f(a, b, c) |
|---|---|---|---|---|
| 0 | 1 | 10 | 100 | 111 |
| 1 | 2 | 20 | 200 | 222 |
| 2 | 3 | 30 | 300 | 333 |
def f(a, b, c):
return a + b + c
print(list(map(f, [1, 2, 3], [10, 20, 30], [100, 200, 300]))) # [111, 222, 333]
Practica con map
- Triplica todos los elementos de una lista de enteros.
- Imprime por pantalla todos los elementos de una lista de strings.
- Convierte los caracteres de una lista a mayúsculas y a minúsculas, y elimina las letras duplicadas de una secuencia.
Ver soluciones propuestas · no incluidas en el material
# 1. Triplicar
print(list(map(lambda x: x * 3, [1, 2, 3, 4]))) # [3, 6, 9, 12]
# 2. Imprimir: map es perezoso, hay que consumirlo para que se ejecute print
list(map(print, ["cat", "dog"]))
# 3. Mayúsculas / minúsculas y sin duplicados
letras = ["a", "B", "c"]
print(list(map(str.upper, letras))) # ['A', 'B', 'C']
print(list(map(str.lower, letras))) # ['a', 'b', 'c']
# dict.fromkeys conserva el orden y descarta repetidos
print("".join(dict.fromkeys(map(str.lower, "Mississippi")))) # misp
Operador filter()
Como map, aplica una función a cada elemento, pero la usa para seleccionar: la función devuelve un booleano y filter conserva los elementos cuyo resultado es True.
filter(<f>, <iterable>)
def greater_than_100(x):
return x > 100
list(filter(greater_than_100, [1, 111, 2, 222, 3, 333])) # [111, 222, 333]
list(filter(lambda x: x > 100, [1, 111, 2, 222, 3, 333])) # igual, con lambda
A diferencia de map, filter solo acepta un iterable.
Practica con filter
- Devuelve los números pares de una lista (defínela con
range). - Imprime las palabras escritas en MAYÚSCULAS (
.isupper) de["cat", "Cat", "CAT", "dog", "Dog", "DOG", "emu", "Emu", "EMU"].
Ver soluciones propuestas · no incluidas en el material
# 1. Pares
print(list(filter(lambda n: n % 2 == 0, range(10)))) # [0, 2, 4, 6, 8]
# 2. Palabras en mayúsculas: str.isupper ya es una función que devuelve bool
palabras = ["cat", "Cat", "CAT", "dog", "Dog", "DOG", "emu", "Emu", "EMU"]
print(list(filter(str.isupper, palabras))) # ['CAT', 'DOG', 'EMU']
Operador reduce()
Aplica una función a los elementos por pares, combinándolos hasta obtener un único resultado. No está en el núcleo de Python: se importa de functools (instalada por defecto).
from functools import reduce
reduce(<f>, <iterable>)
from functools import reduce
def f(x, y):
return x + y
reduce(f, [1, 2, 3, 4, 5]) # 15
El material lo describe como [(1+2)+(3+4)+5], pero reduce acumula de izquierda a derecha: ((((1+2)+3)+4)+5). Con la suma da igual, pero con operaciones no asociativas (resta, división) el orden importa.
reduce no impone tipos, así que no se limita a la aritmética:
reduce(f, ["cat", "dog", "hedgehog", "gecko"])
# 'catdoghedgehoggecko'
lista = ["cat", "dog", "hedgehog", "gecko"]
spaced_list = map(lambda s: s + " ", lista) # combinando map y reduce
reduce(f, spaced_list)
# 'cat dog hedgehog gecko '
Máximo con reduce
Implementa una función que, mediante reduce, calcule el valor máximo de una lista.
Ver solución propuesta · no incluida en el material
from functools import reduce
def maximo(lista):
# en cada paso se queda con el mayor de los dos
return reduce(lambda a, b: a if a > b else b, lista)
print(maximo([3, 17, 4, 9])) # 17
El material incluye vídeos (entornos virtuales, lambdas, resumen de operadores y corrección de OMDB) que no están disponibles en la exportación.
Cierre
Conclusiones de la unidad
Paradigmas
Imperativo, declarativo, reactivo, orientado a objetos, funcional y multiparadigma: conocerlos permite elegir con criterio o combinarlos.
Imperativo vs. declarativo
Instrucciones secuenciales claras frente a describir el resultado, con más abstracción y posibilidad de optimizar sin tocar la lógica.
Python y entornos virtuales
Lenguaje multiparadigma; los venv dan entornos aislados, consistentes y reproducibles.
Funcional en Python
Funciones puras, inmutabilidad y composición: callbacks, lambda, map, filter y reduce.
No existe un único enfoque correcto: el paradigma depende del problema, las restricciones del proyecto y las características del lenguaje.
Autoevaluación
Repaso rápido
Intenta responder antes de desplegar cada pregunta.
¿Qué es un paradigma de programación y en qué dos grandes bloques se dividen?
Un modelo que define un estilo fundamental de programación. Se dividen en imperativo y declarativo.
¿Qué caracteriza a la programación imperativa?
Instrucciones paso a paso, ejecutadas secuencialmente, que cambian el estado del programa, con salida determinista. Incluye estructurada, procedimental y orientada a objetos.
¿Qué problema resolvió la programación estructurada y cuándo surgió?
Surgió en 1966 con el teorema de la programación estructurada y eliminó el uso de GoTo, que perjudicaba la legibilidad y la mantenibilidad (spaghetti code).
¿Cuáles son los cuatro principios de la POO?
Encapsulación, abstracción, herencia y polimorfismo.
¿Por qué la programación reactiva usa menos CPU que el polling?
Porque usa el patrón Observer: los datos se notifican a los interesados y la CPU solo trabaja cuando hay una notificación, en lugar de preguntar continuamente.
¿Cuáles son las cuatro propiedades de un sistema reactivo?
Responsivo, resiliente, elástico y orientado a mensajes.
¿Para qué sirve un entorno virtual y cómo se crea?
Para aislar las dependencias de cada proyecto y evitar conflictos de versiones. Se crea con python -m venv nombre.
¿Qué es una función pura?
La que siempre devuelve el mismo resultado para los mismos argumentos y no tiene efectos secundarios.
¿Qué dos condiciones deben cumplir las funciones para soportar el paradigma funcional?
Poder recibir una función como argumento y poder devolver una función.
¿Qué diferencia hay entre outer(inner) y outer(inner())?
En el primero se pasa la función (callback) y la ejecuta outer. En el segundo se ejecuta inner primero y se pasa su resultado.
¿Qué hace outer()()?
El primer () ejecuta outer, que devuelve una función; el segundo ejecuta esa función devuelta.
¿Cuál es la principal limitación de una lambda?
Solo puede contener una única expresión.
¿Qué devuelve map y qué devuelve filter? ¿Cuántos iterables aceptan?
Ambos devuelven un iterador. map transforma cada elemento y acepta varios iterables; filter conserva los elementos cuyo resultado es True y solo acepta uno.
¿De dónde se importa reduce y qué devuelve?
De functools. Combina los elementos por pares, de izquierda a derecha, hasta devolver un único valor.
¿Por qué no se debe usar is para comparar status_code con 200?
Porque is compara la identidad de los objetos, no su valor. Para números hay que usar ==.