Python

Python

Tutorial Python: Paradigma funcional

Aprende el paradigma funcional en Python con funciones puras, inmutabilidad y funciones como objetos para código predecible y modular.

Aprende Python y certifícate

Funciones puras

En el corazón del paradigma funcional encontramos las funciones puras, uno de los conceptos fundamentales que distingue este enfoque de la programación imperativa tradicional. Una función pura es aquella que cumple dos características esenciales:

  1. Dado el mismo input, siempre produce el mismo output
  2. No tiene efectos secundarios (no modifica estados fuera de su ámbito)

Estas propiedades hacen que las funciones puras sean predecibles, fáciles de probar y razonar sobre ellas. Veamos cómo se aplica este concepto en Python.

Identificando funciones puras

Para entender mejor qué es una función pura, comparemos ejemplos de funciones puras e impuras:

# Función pura
def sumar(a, b):
    return a + b

# Función pura
def duplicar_elementos(lista):
    return [x * 2 for x in lista]

Estas funciones son puras porque:

  • Siempre devuelven el mismo resultado para los mismos argumentos
  • No modifican variables externas ni los argumentos recibidos
  • No realizan operaciones de E/S (entrada/salida)

En contraste, veamos algunas funciones impuras:

total = 0

# Función impura: modifica una variable global
def agregar_al_total(valor):
    global total
    total += valor
    return total

# Función impura: tiene efectos secundarios (E/S)
def guardar_resultado(resultado):
    with open("resultado.txt", "w") as f:
        f.write(str(resultado))
    return True

# Función impura: depende del estado externo
def obtener_temperatura():
    # Imaginemos que esto consulta un servicio web
    return requests.get("https://api.clima.com/temperatura").json()["valor"]

Beneficios de las funciones puras

Las funciones puras ofrecen varias ventajas:

  • Facilidad de prueba: Al no depender de estados externos, son más fáciles de probar de forma aislada.
  • Predictibilidad: Siempre producen el mismo resultado para las mismas entradas.
  • Transparencia referencial: Pueden ser reemplazadas por su valor de retorno sin cambiar el comportamiento del programa.
  • Paralelización: Al no compartir estado, pueden ejecutarse en paralelo sin problemas de concurrencia.

Aplicando funciones puras en código real

Veamos cómo podemos refactorizar código impuro para hacerlo más funcional:

# Enfoque imperativo (impuro)
def calcular_precios_con_impuesto(productos):
    for i in range(len(productos)):
        productos[i]['precio'] = productos[i]['precio'] * 1.21
    return productos

# Enfoque funcional (puro)
def calcular_precios_con_impuesto_puro(productos):
    return [
        {**producto, 'precio': producto['precio'] * 1.21}
        for producto in productos
    ]

En el primer caso, estamos modificando la lista original, lo que constituye un efecto secundario. En el segundo caso, creamos una nueva lista sin modificar la original, manteniendo la pureza de la función.

Composición de funciones puras

Una de las ventajas de las funciones puras es que se pueden componer fácilmente para crear funciones más complejas:

def filtrar_mayores_edad(personas):
    return [p for p in personas if p['edad'] >= 18]

def ordenar_por_nombre(personas):
    return sorted(personas, key=lambda p: p['nombre'])

def obtener_adultos_ordenados(personas):
    return ordenar_por_nombre(filtrar_mayores_edad(personas))

Cada función realiza una tarea específica y se pueden combinar para lograr comportamientos más complejos.

Funciones puras con argumentos por defecto

Las funciones puras pueden tener argumentos por defecto, pero hay que tener cuidado con los objetos mutables:

# ¡Cuidado! Esta función no es pura debido al argumento mutable por defecto
def agregar_item_impura(item, lista=[]):
    lista.append(item)
    return lista

# Versión pura con argumento por defecto
def agregar_item_pura(item, lista=None):
    if lista is None:
        lista = []
    return lista + [item]  # Crea una nueva lista en lugar de modificar la original

Funciones puras con decoradores

Podemos usar decoradores para transformar funciones impuras en puras o para añadir funcionalidad sin comprometer la pureza:

import functools

def memoize(func):
    """Decorador que cachea los resultados de una función pura."""
    cache = {}
    
    @functools.wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    
    return wrapper

@memoize
def fibonacci(n):
    """Función pura que calcula el n-ésimo número de Fibonacci."""
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

Este decorador memoize aprovecha la propiedad de las funciones puras (mismo input, mismo output) para cachear resultados y mejorar el rendimiento sin afectar la pureza de la función.

Manejo de errores en funciones puras

Las funciones puras también deben manejar los errores de forma predecible:

def dividir_seguro(a, b):
    """Función pura que maneja el caso de división por cero."""
    if b == 0:
        return None  # O podríamos usar Optional[float] con tipado
    return a / b

# Alternativa usando excepciones (sigue siendo pura)
def raiz_cuadrada(x):
    """Función pura que calcula la raíz cuadrada si es posible."""
    if x < 0:
        raise ValueError("No se puede calcular la raíz cuadrada de un número negativo")
    return x ** 0.5

Funciones puras en el procesamiento de datos

Las funciones puras son especialmente útiles en el procesamiento de datos:

def normalizar_datos(datos):
    """Normaliza una lista de números al rango [0,1]."""
    if not datos:
        return []
    
    minimo = min(datos)
    maximo = max(datos)
    
    # Evitamos división por cero
    if maximo == minimo:
        return [0.5 for _ in datos]
    
    return [(x - minimo) / (maximo - minimo) for x in datos]

# Uso
temperaturas = [5, 10, 15, 20, 25]
temperaturas_normalizadas = normalizar_datos(temperaturas)
# [0.0, 0.25, 0.5, 0.75, 1.0]

Esta función es pura porque no modifica los datos originales y siempre devuelve el mismo resultado para la misma entrada.

Limitaciones prácticas

Aunque las funciones puras son ideales, en aplicaciones reales a menudo necesitamos interactuar con el mundo exterior (bases de datos, archivos, APIs). Una estrategia común es:

  1. Mantener el núcleo de la lógica de negocio en funciones puras
  2. Aislar las operaciones impuras (E/S) en capas específicas
  3. Minimizar el alcance de los efectos secundarios
# Capa de E/S (impura)
def obtener_datos_usuario(usuario_id):
    return database.query(f"SELECT * FROM usuarios WHERE id = {usuario_id}")

# Capa de lógica de negocio (pura)
def calcular_descuento(historial_compras, total_actual):
    if sum(compra['total'] for compra in historial_compras) > 1000:
        return total_actual * 0.1
    return 0

# Capa de coordinación
def procesar_compra(usuario_id, total):
    # Parte impura
    historial = obtener_datos_usuario(usuario_id)
    
    # Parte pura
    descuento = calcular_descuento(historial, total)
    
    # Parte impura
    database.execute(f"UPDATE usuarios SET descuento = {descuento} WHERE id = {usuario_id}")
    
    return total - descuento

Esta separación nos permite aprovechar los beneficios de las funciones puras donde más importa, manteniendo la capacidad de interactuar con sistemas externos.

Inmutabilidad

La inmutabilidad es un principio fundamental del paradigma funcional que complementa el concepto de funciones puras. En esencia, un objeto inmutable es aquel cuyo estado no puede ser modificado después de su creación. En lugar de cambiar objetos existentes, en programación funcional creamos nuevas versiones con las modificaciones deseadas.

Python ofrece tanto estructuras de datos mutables (listas, diccionarios, conjuntos) como inmutables (tuplas, cadenas, números). Entender y aprovechar la inmutabilidad nos permite escribir código más predecible y menos propenso a errores.

Tipos inmutables en Python

Python incluye varios tipos de datos inmutables por defecto:

  • Números (int, float, complex)
  • Cadenas (str)
  • Tuplas (tuple)
  • Frozensets
  • Bytes

Veamos cómo se comportan estos tipos inmutables:

# Las cadenas son inmutables
nombre = "Python"
# nombre[0] = "J"  # Esto generaría un TypeError

# Para "modificar" una cadena, realmente creamos una nueva
nuevo_nombre = "J" + nombre[1:]  # Creamos "Jython"

# Las tuplas también son inmutables
coordenadas = (10, 20)
# coordenadas[0] = 15  # Esto generaría un TypeError

Ventajas de la inmutabilidad

La inmutabilidad ofrece varios beneficios importantes:

  • Seguridad en concurrencia: Los objetos inmutables son seguros para compartir entre hilos sin necesidad de mecanismos de bloqueo.
  • Predictibilidad: El estado de un objeto inmutable no cambiará inesperadamente.
  • Hashabilidad: Los objetos inmutables pueden usarse como claves en diccionarios o elementos en conjuntos.
  • Razonamiento más sencillo: Es más fácil razonar sobre código que no modifica el estado existente.

Trabajando con colecciones inmutables

Las tuplas son la colección inmutable más común en Python:

# Tupla como estructura de datos inmutable
punto = (3, 4)
distancia = (punto[0]**2 + punto[1]**2)**0.5  # Calculamos sin modificar

# Tuplas con nombres de campos para mayor claridad
from collections import namedtuple

Punto = namedtuple('Punto', ['x', 'y'])
p = Punto(3, 4)
print(p.x, p.y)  # Acceso por nombre: 3 4

Para conjuntos inmutables, podemos usar frozenset:

# Conjunto normal (mutable)
colores = {"rojo", "verde", "azul"}
colores.add("amarillo")  # Esto funciona

# Conjunto inmutable
colores_fijos = frozenset(["rojo", "verde", "azul"])
# colores_fijos.add("amarillo")  # Esto generaría un AttributeError

Transformaciones inmutables

Cuando trabajamos con inmutabilidad, en lugar de modificar estructuras existentes, creamos nuevas versiones:

# Enfoque imperativo (mutable)
def agregar_impuesto_mutable(productos):
    for producto in productos:
        producto['precio'] *= 1.21
    return productos

# Enfoque funcional (inmutable)
def agregar_impuesto_inmutable(productos):
    return [
        {**producto, 'precio': producto['precio'] * 1.21}
        for producto in productos
    ]

# Uso
productos_originales = [{'nombre': 'Laptop', 'precio': 1000}, 
                        {'nombre': 'Mouse', 'precio': 20}]

# La versión mutable modifica la lista original
productos_con_impuesto = agregar_impuesto_mutable(productos_originales)
# productos_originales ahora está modificado

# Reiniciamos para el ejemplo inmutable
productos_originales = [{'nombre': 'Laptop', 'precio': 1000}, 
                        {'nombre': 'Mouse', 'precio': 20}]

# La versión inmutable crea una nueva lista
productos_con_impuesto = agregar_impuesto_inmutable(productos_originales)
# productos_originales permanece intacto

Patrones para mantener la inmutabilidad

Existen varios patrones que nos ayudan a trabajar con inmutabilidad en Python:

  • 1. Copiar antes de modificar:
# Con listas
numeros = [1, 2, 3, 4, 5]
numeros_duplicados = [n * 2 for n in numeros]  # Nueva lista

# Con diccionarios
config = {'debug': True, 'timeout': 30}
config_actualizada = {**config, 'timeout': 60}  # Nuevo diccionario
  • 2. Usar métodos que devuelven nuevas instancias:
texto = "hola mundo"
texto_mayusculas = texto.upper()  # Devuelve una nueva cadena

numeros = (1, 2, 3)
mas_numeros = numeros + (4, 5)  # Devuelve una nueva tupla
  • 3. Funciones de transformación:
def incrementar(x):
    return x + 1

numeros = [1, 2, 3]
incrementados = list(map(incrementar, numeros))  # Nueva lista [2, 3, 4]

Inmutabilidad en clases personalizadas

Podemos crear nuestras propias clases inmutables en Python:

class Rectangulo:
    def __init__(self, ancho, alto):
        self._ancho = ancho
        self._alto = alto
    
    @property
    def ancho(self):
        return self._ancho
    
    @property
    def alto(self):
        return self._alto
    
    @property
    def area(self):
        return self._ancho * self._alto
    
    def redimensionar(self, nuevo_ancho, nuevo_alto):
        # En lugar de modificar, devolvemos una nueva instancia
        return Rectangulo(nuevo_ancho, nuevo_alto)

# Uso
rect = Rectangulo(10, 20)
rect_grande = rect.redimensionar(20, 30)  # Nueva instancia

Inmutabilidad con dataclasses

Las dataclasses de Python (disponibles desde Python 3.7) facilitan la creación de clases inmutables:

from dataclasses import dataclass

@dataclass(frozen=True)  # El parámetro frozen=True hace la clase inmutable
class Punto3D:
    x: float
    y: float
    z: float
    
    def distancia_al_origen(self):
        return (self.x**2 + self.y**2 + self.z**2)**0.5

# Uso
p = Punto3D(3, 4, 5)
# p.x = 10  # Esto generaría un error por ser inmutable

Inmutabilidad y rendimiento

La inmutabilidad puede tener implicaciones en el rendimiento:

# Operaciones con cadenas (inmutables)
def construir_cadena_ineficiente(n):
    resultado = ""
    for i in range(n):
        resultado += str(i)  # Crea una nueva cadena en cada iteración
    return resultado

# Mejor enfoque para cadenas
def construir_cadena_eficiente(n):
    partes = []
    for i in range(n):
        partes.append(str(i))
    return "".join(partes)  # Una sola operación de concatenación

Para colecciones grandes que requieren muchas modificaciones, a veces es más eficiente usar estructuras mutables durante el procesamiento y convertir a inmutables al final:

# Procesamiento eficiente con enfoque híbrido
def procesar_datos(datos_entrada):
    # Usamos una estructura mutable durante el procesamiento
    resultados = []
    
    for dato in datos_entrada:
        # Varias operaciones de procesamiento
        valor_procesado = dato * 2 + 1
        resultados.append(valor_procesado)
    
    # Convertimos a inmutable al finalizar
    return tuple(resultados)

Inmutabilidad y bibliotecas externas

Algunas bibliotecas de Python promueven la inmutabilidad para facilitar la programación funcional:

# Ejemplo con pyrsistent para estructuras de datos inmutables persistentes
from pyrsistent import pvector, pmap

# Vector inmutable
v1 = pvector([1, 2, 3])
v2 = v1.append(4)  # Crea un nuevo vector
# v1 sigue siendo [1, 2, 3]
# v2 es [1, 2, 3, 4]

# Mapa inmutable
m1 = pmap({"a": 1, "b": 2})
m2 = m1.set("c", 3)  # Crea un nuevo mapa
# m1 sigue siendo {"a": 1, "b": 2}
# m2 es {"a": 1, "b": 2, "c": 3}

Inmutabilidad y funciones de orden superior

La inmutabilidad se combina perfectamente con funciones de orden superior como map, filter y reduce:

from functools import reduce

# Transformación de datos inmutable con map
numeros = [1, 2, 3, 4, 5]
cuadrados = list(map(lambda x: x**2, numeros))  # [1, 4, 9, 16, 25]

# Filtrado inmutable
pares = list(filter(lambda x: x % 2 == 0, numeros))  # [2, 4]

# Reducción inmutable
suma = reduce(lambda x, y: x + y, numeros)  # 15

La inmutabilidad es un pilar fundamental de la programación funcional que, junto con las funciones puras, nos permite escribir código más predecible, seguro y fácil de razonar. Aunque Python no impone la inmutabilidad como otros lenguajes funcionales, nos proporciona las herramientas necesarias para aplicar este principio cuando sea beneficioso.

Funciones como objetos

En Python, las funciones son objetos de primera clase, lo que significa que pueden ser tratadas como cualquier otro tipo de dato. Esta característica es fundamental para el paradigma de programación funcional y diferencia a Python de lenguajes puramente imperativos. Entender este concepto abre un mundo de posibilidades para escribir código más elegante y expresivo.

Funciones como valores

A diferencia de algunos lenguajes donde las funciones son solo bloques de código, en Python podemos:

  • Asignar funciones a variables
  • Pasar funciones como argumentos
  • Devolver funciones desde otras funciones
  • Almacenar funciones en estructuras de datos

Veamos un ejemplo sencillo:

def saludar(nombre):
    return f"Hola, {nombre}!"

# Asignamos la función a una variable
mi_funcion = saludar

# Usamos la variable como si fuera la función original
resultado = mi_funcion("Ana")
print(resultado)  # Imprime: Hola, Ana!

Observa que al asignar saludar a mi_funcion, no usamos paréntesis. Esto es porque queremos la función en sí misma, no el resultado de ejecutarla.

Funciones como argumentos

Una de las aplicaciones más potentes es pasar funciones como argumentos a otras funciones:

def aplicar_operacion(func, valor):
    return func(valor)

def duplicar(x):
    return x * 2

def cuadrado(x):
    return x ** 2

# Pasamos diferentes funciones como primer argumento
resultado1 = aplicar_operacion(duplicar, 5)  # 10
resultado2 = aplicar_operacion(cuadrado, 5)  # 25

Este patrón es la base de muchas operaciones de orden superior en programación funcional, como map, filter y reduce.

Funciones anónimas (lambda)

Python permite crear funciones sin nombre mediante la expresión lambda. Son útiles cuando necesitamos una función simple para usar una sola vez:

# Función lambda que suma dos números
sumar = lambda x, y: x + y
print(sumar(3, 4))  # 7

# Uso de lambda directamente como argumento
numeros = [1, 5, 3, 9, 2, 6]
ordenados = sorted(numeros, key=lambda x: abs(x - 5))
print(ordenados)  # [5, 6, 3, 9, 2, 1] (ordenados por cercanía al número 5)

Las funciones lambda están limitadas a una sola expresión, pero son muy útiles para operaciones simples sin necesidad de definir una función completa.

Funciones de orden superior

Las funciones de orden superior son aquellas que toman otras funciones como argumentos o devuelven funciones. Python incluye varias funciones de orden superior en su biblioteca estándar:

  • map(): Aplica una función a cada elemento de un iterable
numeros = [1, 2, 3, 4, 5]
cuadrados = map(lambda x: x**2, numeros)
print(list(cuadrados))  # [1, 4, 9, 16, 25]
  • filter(): Filtra elementos de un iterable según una función de predicado
numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pares = filter(lambda x: x % 2 == 0, numeros)
print(list(pares))  # [2, 4, 6, 8, 10]
  • functools.reduce(): Reduce un iterable a un solo valor aplicando una función acumulativa
from functools import reduce

numeros = [1, 2, 3, 4, 5]
producto = reduce(lambda x, y: x * y, numeros)
print(producto)  # 120 (1*2*3*4*5)

Closures: funciones que recuerdan su contexto

Un closure es una función que "recuerda" el entorno en el que fue creada, incluso cuando se ejecuta fuera de ese entorno:

def crear_multiplicador(factor):
    # La función interna "recuerda" el valor de factor
    def multiplicar(numero):
        return numero * factor
    return multiplicar

duplicar = crear_multiplicador(2)
triplicar = crear_multiplicador(3)

print(duplicar(5))  # 10
print(triplicar(5))  # 15

En este ejemplo, duplicar y triplicar son closures que "recuerdan" los valores de factor con los que fueron creados.

Decoradores: modificando el comportamiento de funciones

Los decoradores son una aplicación poderosa de las funciones como objetos. Permiten modificar o extender el comportamiento de otras funciones:

def registrar_llamada(func):
    def wrapper(*args, **kwargs):
        print(f"Llamando a {func.__name__} con {args} y {kwargs}")
        resultado = func(*args, **kwargs)
        print(f"La función {func.__name__} devolvió {resultado}")
        return resultado
    return wrapper

@registrar_llamada
def suma(a, b):
    return a + b

resultado = suma(3, 5)
# Imprime:
# Llamando a suma con (3, 5) y {}
# La función suma devolvió 8

El decorador @registrar_llamada es azúcar sintáctico para suma = registrar_llamada(suma).

Funciones parciales

La biblioteca functools proporciona partial, que permite crear nuevas funciones fijando algunos argumentos de una función existente:

from functools import partial

def potencia(base, exponente):
    return base ** exponente

# Creamos una función que eleva al cuadrado
al_cuadrado = partial(potencia, exponente=2)
# Creamos una función que calcula la raíz cuadrada
raiz_cuadrada = partial(potencia, exponente=0.5)

print(al_cuadrado(5))      # 25
print(raiz_cuadrada(25))   # 5.0

Atributos de funciones

Como las funciones son objetos, pueden tener atributos:

def contador():
    contador.llamadas += 1
    return contador.llamadas

# Inicializamos el atributo
contador.llamadas = 0

print(contador())  # 1
print(contador())  # 2
print(contador())  # 3
print(contador.llamadas)  # 3

Funciones como elementos de estructuras de datos

Podemos almacenar funciones en estructuras de datos como listas o diccionarios:

def suma(a, b): return a + b
def resta(a, b): return a - b
def multiplicacion(a, b): return a * b
def division(a, b): return a / b if b != 0 else "Error: División por cero"

# Diccionario de operaciones
operaciones = {
    '+': suma,
    '-': resta,
    '*': multiplicacion,
    '/': division
}

# Calculadora simple
def calcular(a, op, b):
    if op in operaciones:
        return operaciones[op](a, b)
    return "Operación no soportada"

print(calcular(10, '+', 5))  # 15
print(calcular(10, '*', 5))  # 50
print(calcular(10, '/', 0))  # Error: División por cero

Este patrón es útil para implementar tablas de dispatch o estrategias intercambiables.

Composición de funciones

La composición de funciones es una técnica donde el resultado de una función se pasa como entrada a otra:

def componer(f, g):
    """Crea una nueva función que aplica f después de g."""
    return lambda x: f(g(x))

# Funciones simples para componer
def duplicar(x): return x * 2
def incrementar(x): return x + 1

# Componemos las funciones
duplicar_y_luego_incrementar = componer(incrementar, duplicar)
incrementar_y_luego_duplicar = componer(duplicar, incrementar)

print(duplicar_y_luego_incrementar(5))  # 11 (5*2 + 1)
print(incrementar_y_luego_duplicar(5))  # 12 ((5+1) * 2)

Para componer múltiples funciones, podemos crear un helper más general:

def componer_multiples(*funciones):
    """Compone múltiples funciones de derecha a izquierda."""
    def compuesta(x):
        resultado = x
        for f in reversed(funciones):
            resultado = f(resultado)
        return resultado
    return compuesta

def cuadrado(x): return x ** 2
def duplicar(x): return x * 2
def incrementar(x): return x + 1

# Componemos tres funciones
pipeline = componer_multiples(cuadrado, duplicar, incrementar)

# Equivalente a cuadrado(duplicar(incrementar(5)))
print(pipeline(5))  # 144 ((5+1)*2)^2

Currificación

La currificación es una técnica donde una función que toma múltiples argumentos se transforma en una secuencia de funciones que toman un solo argumento:

def curry(func):
    """Currifica una función de dos argumentos."""
    def curried(x):
        def inner(y):
            return func(x, y)
        return inner
    return curried

# Función normal de dos argumentos
def multiplicar(x, y):
    return x * y

# Versión currificada
multiplicar_currificado = curry(multiplicar)

# Uso
por_cinco = multiplicar_currificado(5)
print(por_cinco(3))  # 15
print(por_cinco(7))  # 35

La currificación facilita la creación de funciones especializadas a partir de funciones más generales.

Aplicaciones prácticas

Veamos un ejemplo práctico que combina varios conceptos:

# Procesamiento de datos con programación funcional
datos = [
    {"nombre": "Ana", "edad": 25, "ciudad": "Madrid"},
    {"nombre": "Juan", "edad": 17, "ciudad": "Barcelona"},
    {"nombre": "María", "edad": 30, "ciudad": "Madrid"},
    {"nombre": "Pedro", "edad": 22, "ciudad": "Valencia"},
    {"nombre": "Lucía", "edad": 16, "ciudad": "Barcelona"}
]

# Funciones de filtrado
def es_mayor_de_edad(persona):
    return persona["edad"] >= 18

def vive_en(ciudad):
    # Retornamos una función que comprueba la ciudad
    return lambda persona: persona["ciudad"] == ciudad

# Funciones de transformación
def extraer_nombre(persona):
    return persona["nombre"]

def saludar(nombre):
    return f"Hola, {nombre}!"

# Combinamos todo para obtener saludos a mayores de edad de Madrid
from functools import reduce

resultado = (
    # Filtramos mayores de edad
    filter(es_mayor_de_edad, 
           # Filtramos residentes de Madrid
           filter(vive_en("Madrid"), datos))
)

# Extraemos nombres y generamos saludos
saludos = map(saludar, map(extraer_nombre, resultado))

for saludo in saludos:
    print(saludo)
# Imprime:
# Hola, Ana!
# Hola, María!

Este enfoque permite construir pipelines de procesamiento de datos claros y modulares, donde cada función tiene una responsabilidad única.

El tratamiento de funciones como objetos de primera clase es uno de los pilares que hace que Python sea un lenguaje versátil, permitiendo combinar paradigmas de programación y adoptar un estilo funcional cuando resulta beneficioso para la claridad y mantenibilidad del código.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende Python online

Ejercicios de esta lección Paradigma funcional

Evalúa tus conocimientos de esta lección Paradigma funcional con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Módulo math

Python
Puzzle

Reto herencia

Python
Código

Excepciones

Python
Test

Introducción a Python

Python
Test

Reto variables

Python
Código

Funciones Python

Python
Puzzle

Reto funciones

Python
Código

Módulo datetime

Python
Test

Reto acumulación

Python
Código

Reto estructuras condicionales

Python
Código

Polimorfismo

Python
Test

Módulo os

Python
Test

Reto métodos dunder

Python
Código

Diccionarios

Python
Puzzle

Reto clases y objetos

Python
Código

Reto operadores

Python
Código

Operadores

Python
Test

Estructuras de control

Python
Puzzle

Funciones lambda

Python
Test

Reto diccionarios

Python
Código

Reto función lambda

Python
Código

Encapsulación

Python
Puzzle

Reto coleciones

Python
Proyecto

Reto funciones auxiliares

Python
Código

Crear módulos y paquetes

Python
Puzzle

Módulo datetime

Python
Puzzle

Excepciones

Python
Puzzle

Operadores

Python
Puzzle

Diccionarios

Python
Test

Reto map, filter

Python
Código

Reto tuplas

Python
Código

Proyecto gestor de tareas CRUD

Python
Proyecto

Tuplas

Python
Puzzle

Variables

Python
Puzzle

Tipos de datos

Python
Puzzle

Conjuntos

Python
Test

Reto mixins

Python
Código

Módulo csv

Python
Test

Módulo json

Python
Test

Herencia

Python
Test

Análisis de datos de ventas con Pandas

Python
Proyecto

Reto fechas y tiempo

Python
Proyecto

Reto estructuras de iteración

Python
Código

Funciones

Python
Test

Reto comprehensions

Python
Código

Variables

Python
Test

Reto serialización

Python
Proyecto

Módulo csv

Python
Puzzle

Reto polimorfismo

Python
Código

Polimorfismo

Python
Puzzle

Clases y objetos

Python
Código

Reto encapsulación

Python
Código

Estructuras de control

Python
Test

Importar módulos y paquetes

Python
Test

Módulo math

Python
Test

Funciones lambda

Python
Puzzle

Reto excepciones

Python
Código

Listas

Python
Puzzle

Reto archivos

Python
Proyecto

Encapsulación

Python
Test

Reto conjuntos

Python
Código

Clases y objetos

Python
Test

Instalación de Python y creación de proyecto

Python
Test

Reto listas

Python
Código

Tipos de datos

Python
Test

Crear módulos y paquetes

Python
Test

Tuplas

Python
Test

Herencia

Python
Puzzle

Reto acceso a sistema

Python
Proyecto

Proyecto sintaxis calculadora

Python
Proyecto

Importar módulos y paquetes

Python
Puzzle

Clases y objetos

Python
Puzzle

Módulo os

Python
Puzzle

Listas

Python
Test

Conjuntos

Python
Puzzle

Reto tipos de datos

Python
Código

Reto matemáticas

Python
Proyecto

Módulo json

Python
Puzzle

Todas las lecciones de Python

Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Python

Python

Introducción

Instalación Y Creación De Proyecto

Python

Introducción

Tema 2: Tipos De Datos, Variables Y Operadores

Python

Introducción

Instalación De Python

Python

Introducción

Tipos De Datos

Python

Sintaxis

Variables

Python

Sintaxis

Operadores

Python

Sintaxis

Estructuras De Control

Python

Sintaxis

Funciones

Python

Sintaxis

Estructuras Control Iterativo

Python

Sintaxis

Estructuras Control Condicional

Python

Sintaxis

Testing Con Pytest

Python

Sintaxis

Listas

Python

Estructuras De Datos

Tuplas

Python

Estructuras De Datos

Diccionarios

Python

Estructuras De Datos

Conjuntos

Python

Estructuras De Datos

Comprehensions

Python

Estructuras De Datos

Clases Y Objetos

Python

Programación Orientada A Objetos

Excepciones

Python

Programación Orientada A Objetos

Encapsulación

Python

Programación Orientada A Objetos

Herencia

Python

Programación Orientada A Objetos

Polimorfismo

Python

Programación Orientada A Objetos

Mixins Y Herencia Múltiple

Python

Programación Orientada A Objetos

Métodos Especiales (Dunder Methods)

Python

Programación Orientada A Objetos

Composición De Clases

Python

Programación Orientada A Objetos

Funciones Lambda

Python

Programación Funcional

Aplicación Parcial

Python

Programación Funcional

Entrada Y Salida, Manejo De Archivos

Python

Programación Funcional

Decoradores

Python

Programación Funcional

Generadores

Python

Programación Funcional

Paradigma Funcional

Python

Programación Funcional

Composición De Funciones

Python

Programación Funcional

Funciones Orden Superior Map Y Filter

Python

Programación Funcional

Funciones Auxiliares

Python

Programación Funcional

Reducción Y Acumulación

Python

Programación Funcional

Archivos Comprimidos

Python

Entrada Y Salida Io

Entrada Y Salida Avanzada

Python

Entrada Y Salida Io

Archivos Temporales

Python

Entrada Y Salida Io

Contexto With

Python

Entrada Y Salida Io

Módulo Csv

Python

Biblioteca Estándar

Módulo Json

Python

Biblioteca Estándar

Módulo Datetime

Python

Biblioteca Estándar

Módulo Math

Python

Biblioteca Estándar

Módulo Os

Python

Biblioteca Estándar

Módulo Re

Python

Biblioteca Estándar

Módulo Random

Python

Biblioteca Estándar

Módulo Time

Python

Biblioteca Estándar

Módulo Collections

Python

Biblioteca Estándar

Módulo Sys

Python

Biblioteca Estándar

Módulo Statistics

Python

Biblioteca Estándar

Módulo Pickle

Python

Biblioteca Estándar

Módulo Pathlib

Python

Biblioteca Estándar

Importar Módulos Y Paquetes

Python

Paquetes Y Módulos

Crear Módulos Y Paquetes

Python

Paquetes Y Módulos

Entornos Virtuales (Virtualenv, Venv)

Python

Entorno Y Dependencias

Gestión De Dependencias (Pip, Requirements.txt)

Python

Entorno Y Dependencias

Python-dotenv Y Variables De Entorno

Python

Entorno Y Dependencias

Acceso A Datos Con Mysql, Pymongo Y Pandas

Python

Acceso A Bases De Datos

Acceso A Mongodb Con Pymongo

Python

Acceso A Bases De Datos

Acceso A Mysql Con Mysql Connector

Python

Acceso A Bases De Datos

Novedades Python 3.13

Python

Características Modernas

Operador Walrus

Python

Características Modernas

Pattern Matching

Python

Características Modernas

Instalación Beautiful Soup

Python

Web Scraping

Sintaxis General De Beautiful Soup

Python

Web Scraping

Tipos De Selectores

Python

Web Scraping

Web Scraping De Html

Python

Web Scraping

Web Scraping Para Ciencia De Datos

Python

Web Scraping

Autenticación Y Acceso A Recursos Protegidos

Python

Web Scraping

Combinación De Selenium Con Beautiful Soup

Python

Web Scraping

Accede GRATIS a Python y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender qué son las funciones puras y sus propiedades fundamentales.
  • Aprender a aplicar la inmutabilidad en estructuras de datos y clases en Python.
  • Entender cómo las funciones son objetos de primera clase y cómo utilizarlas como valores, argumentos y en composiciones.
  • Conocer técnicas funcionales avanzadas como closures, decoradores, currificación y funciones de orden superior.
  • Aplicar estos conceptos para escribir código más predecible, modular y fácil de mantener.