Higher-order functions y functools: funciones como valores
Una higher-order function acepta funciones como argumentos o retorna funciones. map, filter, sorted y reduce son los ejemplos clásicos, pero puedes crear las tuyas con la misma facilidad.
Funciones como argumentos y retorno
Las higher-order functions son funciones que reciben otras funciones como argumentos o las retornan. Esta capacidad es lo que hace posible los decoradores, callbacks, y el estilo funcional en Python.
from typing import Callable, TypeVar
T = TypeVar("T")
# HOF que acepta una función como argumento
def apply_twice(f: Callable[[T], T], x: T) -> T:
"""Aplica f dos veces: f(f(x))."""
return f(f(x))
print(apply_twice(lambda x: x + 3, 10)) # 16 (10+3+3)
print(apply_twice(str.upper, "hola")) # HOLA (ya está en upper, sin cambio)
print(apply_twice(lambda s: s + "!", "Python")) # Python!!
# HOF que retorna funciones
def make_validator(min_val: float, max_val: float) -> Callable[[float], bool]:
def validate(x: float) -> bool:
return min_val <= x <= max_val
return validate
is_percentage = make_validator(0, 100)
is_temperature_celsius = make_validator(-273.15, 1e6)
print(is_percentage(75)) # True
print(is_percentage(150)) # False
print(is_temperature_celsius(-300)) # False
# HOF built-in: map, filter, sorted
nums = [3, 1, 4, 1, 5, 9, 2, 6]
squared = list(map(lambda x: x ** 2, nums))
print(squared) # [9, 1, 16, 1, 25, 81, 4, 36]
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens) # [4, 2, 6]16
HOLA
Python!!
True
False
False
[9, 1, 16, 1, 25, 81, 4, 36]
[4, 2, 6]sorted con key — el HOF más subestimado
sorted(iterable, key=func) es quizás la higher-order function más usada en Python. La función key se aplica a cada elemento para obtener el valor de comparación — nunca modifica los elementos.
from dataclasses import dataclass
from typing import NamedTuple
# Ordenar strings por longitud
palabras = ["python", "es", "un", "lenguaje", "increíble"]
print(sorted(palabras, key=len))
# ['es', 'un', 'python', 'lenguaje', 'increíble']
# Ordenar por múltiples criterios con tuple
productos = [
{"name": "laptop", "price": 999, "stock": 5},
{"name": "mouse", "price": 25, "stock": 50},
{"name": "teclado", "price": 75, "stock": 5},
{"name": "monitor", "price": 350, "stock": 0},
]
# Primero por stock (los agotados al final), luego por precio
ordenados = sorted(
productos,
key=lambda p: (p["stock"] == 0, p["price"])
)
for p in ordenados:
print(f"{p['name']:10} ${p['price']:4} stock:{p['stock']}")
# Con dataclasses: usar attrgetter para mayor eficiencia
from operator import attrgetter, itemgetter
@dataclass
class Employee:
name: str
department: str
salary: float
employees = [
Employee("Ana", "Ing", 80_000),
Employee("Carlos", "Design", 65_000),
Employee("Diana", "Ing", 95_000),
Employee("Eva", "Design", 70_000),
]
# Ordenar por departamento y luego por salario descendente
sorted_emps = sorted(
employees,
key=lambda e: (e.department, -e.salary)
)
for e in sorted_emps:
print(f"{e.name:10} {e.department:8} ${e.salary:,.0f}")['es', 'un', 'python', 'lenguaje', 'increíble']
laptor $ 25 stock:50
teclado $ 75 stock:5
laptop $ 999 stock:5
monitor $ 350 stock:0
Carlos Design $65,000
Eva Design $70,000
Diana Ing $95,000
Ana Ing $80,000Composición funcional con functools.reduce
functools.reduce(f, iterable) aplica acumulativamente una función de dos argumentos a los elementos del iterable. Es la HOF más genérica: suma, producto, max, min y la composición pueden expresarse con reduce.
from functools import reduce
from typing import Callable, TypeVar
T = TypeVar("T")
# reduce básico: suma acumulativa
nums = [1, 2, 3, 4, 5]
total = reduce(lambda acc, x: acc + x, nums)
print(total) # 15
# reduce para máximo (reimplementar max)
mi_max = reduce(lambda a, b: a if a > b else b, nums)
print(mi_max) # 5
# reduce para aplanar listas
nested = [[1, 2], [3, 4], [5, 6]]
flat = reduce(lambda acc, lst: acc + lst, nested, [])
print(flat) # [1, 2, 3, 4, 5, 6]
# Composición de funciones con reduce
def compose(*functions: Callable) -> Callable:
"""Compone N funciones: compose(f, g, h)(x) = f(g(h(x)))."""
return reduce(lambda f, g: lambda x: f(g(x)), functions)
strip = str.strip
lower = str.lower
normalize_spaces = lambda s: " ".join(s.split())
normalize = compose(lower, strip, normalize_spaces)
print(normalize(" Hola MUNDO ")) # hola mundo
# Pipeline funcional con reduce
def pipe(*functions: Callable) -> Callable:
"""Aplica funciones de izquierda a derecha."""
return reduce(lambda f, g: lambda x: g(f(x)), functions)
process = pipe(
lambda s: s.strip(),
str.lower,
lambda s: s.replace(" ", "-"),
lambda s: f"slug:{s}",
)
print(process(" Hello World ")) # slug:hello-world15
5
[1, 2, 3, 4, 5, 6]
hola mundo
slug:hello-worldfunctools.partial — pre-configurar funciones
partial(func, *args, **kwargs) retorna una nueva función con algunos argumentos ya fijados. Es una forma de especializar funciones genéricas sin escribir lambdas adicionales.
from functools import partial
# Especializar funciones genéricas
def power(base: float, exponent: float) -> float:
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(4)) # 16.0
print(cube(3)) # 27.0
# Caso real: configurar funciones de logging
import logging
def log_event(level: int, logger_name: str, message: str) -> None:
logging.getLogger(logger_name).log(level, message)
log_info = partial(log_event, logging.INFO, "app")
log_error = partial(log_event, logging.ERROR, "app")
# Ahora se usan con un solo argumento
log_info("Servidor iniciado")
log_error("Conexión fallida")
# partial con sorted para crear ordenadores especializados
from operator import itemgetter
sort_by_price = partial(sorted, key=itemgetter("price"))
sort_by_name = partial(sorted, key=itemgetter("name"))
items = [
{"name": "Cámara", "price": 599},
{"name": "Auriculares", "price": 199},
{"name": "Trípode", "price": 79},
]
print([p["name"] for p in sort_by_price(items)])
# ['Trípode', 'Auriculares', 'Cámara']
print([p["name"] for p in sort_by_name(items)])
# ['Auriculares', 'Cámara', 'Trípode']16.0
27.0
['Trípode', 'Auriculares', 'Cámara']
['Auriculares', 'Cámara', 'Trípode']