functools.partial en Python permite crear una nueva función a partir de otra, fijando de antemano algunos argumentos. Es una herramienta pequeña, pero muy útil cuando necesitas adaptar callbacks, reducir configuración repetida, preparar una función para una API con una firma concreta o hacer más legibles los flujos de procesamiento.
En esta guía aprenderás cómo funciona partial(), cuándo mejora el código, qué errores conviene evitar y cómo combinarlo con funciones, métodos, callbacks, type hints y pruebas. El contenido complementa nuestras guías sobre funciones en Python, funciones lambda, decoradores, args y kwargs y type hints.
Qué hace functools.partial
partial() recibe una función y una serie de argumentos posicionales o nombrados. Devuelve un objeto invocable que ejecuta la función original con esos valores ya incluidos.
from functools import partial
def potencia(base, exponente):
return base ** exponente
cuadrado = partial(potencia, exponente=2)
print(cuadrado(5)) # 25La función original no cambia. El objeto cuadrado recuerda que exponente vale 2. Por eso, llamar cuadrado(5) equivale a ejecutar potencia(5, exponente=2).
Por qué fijar argumentos
Imagina una aplicación que convierte muchos valores con la misma tasa:
def convertir(valor, tasa, redondear=True):
resultado = valor * tasa
return round(resultado, 2) if redondear else resultado
convertir_local = partial(convertir, tasa=5.42)
print(convertir_local(10))
print(convertir_local(25))Sin partial, tendrías que repetir tasa=5.42 o escribir una función envolvente. La versión parcial expresa claramente que se trata de una operación especializada.
Argumentos posicionales
Los argumentos posicionales almacenados se insertan antes de los argumentos enviados en la llamada posterior.
def rodear(prefijo, texto, sufijo):
return f"{prefijo}{texto}{sufijo}"
abrir_corchete = partial(rodear, "[")
print(abrir_corchete("Python", "]"))El resultado es [Python]. Como el orden importa, conviene usar argumentos posicionales solo cuando la firma es sencilla y estable.
Argumentos nombrados
Los argumentos nombrados suelen ser más claros porque muestran la finalidad de cada valor fijo.
import json
from functools import partial
json_legible = partial(
json.dumps,
ensure_ascii=False,
indent=2,
sort_keys=True,
)
print(json_legible({"curso": "Python", "activo": True}))Este patrón crea un serializador configurado una sola vez. La documentación oficial de functools.partial explica cómo se combinan los argumentos almacenados con los nuevos.
Sobrescribir keywords almacenadas
Un argumento nombrado guardado en el partial puede reemplazarse en la llamada:
salida_json = partial(json.dumps, ensure_ascii=False, indent=2)
print(salida_json({"a": 1}, indent=None))El valor más reciente prevalece. Esta flexibilidad es útil, pero si casi todas las llamadas reemplazan la configuración, probablemente la especialización no sea significativa.
Adaptar callbacks
Muchas bibliotecas invocan callbacks con una firma fija. Un partial puede adjuntar contexto sin depender de variables globales.
def registrar_evento(categoria, mensaje):
print(f"[{categoria}] {mensaje}")
registrar_error = partial(registrar_evento, "ERROR")
registrar_info = partial(registrar_evento, "INFO")
registrar_error("Archivo no encontrado")
registrar_info("Proceso terminado")Este patrón funciona bien en interfaces gráficas, colas, schedulers, sistemas de eventos y frameworks web.
Usar partial con map
def descuento(valor, porcentaje):
return valor * (1 - porcentaje)
diez_por_ciento = partial(descuento, porcentaje=0.10)
precios = [100, 250, 80]
resultado = list(map(diez_por_ciento, precios))Cada precio se procesa con la misma función especializada. Una comprensión de lista también puede ser clara; elige la forma que describa mejor el flujo.
Usar partial como clave de ordenación
def distancia(objetivo, valor):
return abs(valor - objetivo)
cerca_de_diez = partial(distancia, 10)
valores = [1, 20, 8, 13, 5]
print(sorted(valores, key=cerca_de_diez))El código comunica de forma explícita que la ordenación depende de la distancia hasta 10.
partial frente a lambda
Una lambda puede resolver muchos de los mismos casos:
cuadrado_lambda = lambda base: potencia(base, exponente=2)
cuadrado_partial = partial(potencia, exponente=2)Usa partial cuando la intención sea únicamente rellenar argumentos. Usa lambda cuando necesites transformar datos, combinar llamadas o aplicar una condición.
partial frente a una función wrapper
Una función nombrada permite añadir documentación, validación y lenguaje del dominio.
def cuadrado(base):
"""Devuelve el cuadrado de un número."""
return potencia(base, 2)Este formato suele ser mejor para APIs públicas o reglas importantes. Partial funciona mejor como adaptación directa y local.
Inspeccionar un objeto partial
Los objetos parciales exponen tres atributos:
func, la función original;args, los argumentos posicionales fijados;keywords, los argumentos nombrados fijados.
print(cuadrado.func)
print(cuadrado.args)
print(cuadrado.keywords)Estos atributos facilitan la depuración y las pruebas específicas.
Nombre y documentación
Un objeto partial no recibe automáticamente el __name__ ni la docstring de la función original. Puedes copiar metadatos con functools.update_wrapper().
from functools import partial, update_wrapper
cuadrado = partial(potencia, exponente=2)
update_wrapper(cuadrado, potencia)Aun así, el nombre copiado sigue describiendo la función original y no necesariamente la especialización. En una API pública, un wrapper nombrado suele ser más expresivo.
partialmethod en clases
Para clases existe functools.partialmethod. Esta variante coopera con el protocolo de descriptors para enlazar correctamente la instancia.
from functools import partialmethod
class Publicador:
def enviar(self, mensaje, nivel):
print(nivel, mensaje)
informar = partialmethod(enviar, nivel="INFO")
alertar = partialmethod(enviar, nivel="ALERTA")
p = Publicador()
p.informar("Servidor iniciado")Consulta la documentación oficial de partialmethod para entender su relación con descriptors.
Argumentos mutables
Los argumentos se almacenan por referencia. Si fijas una lista o un diccionario y después lo modificas, las llamadas futuras verán el objeto cambiado.
config = {"modo": "prueba"}
def ejecutar(nombre, config):
return nombre, config
ejecutar_configurado = partial(ejecutar, config=config)
config["modo"] = "producción"
print(ejecutar_configurado("backup"))Puede ser intencional, pero también causa errores sutiles. Prefiere valores inmutables o copia la configuración en el punto adecuado.
Firmas e introspección
inspect.signature() suele producir una firma adaptada para un partial:
from inspect import signature
print(signature(cuadrado))Esto ayuda a IDEs y herramientas de validación, aunque algunos frameworks necesitan un wrapper explícito para conservar metadatos especiales.
Type hints
Los analizadores estáticos pueden inferir algunos usos, pero las firmas complejas pueden quedar poco claras. Una anotación explícita ayuda:
from collections.abc import Callable
cuadrado_tipado: Callable[[float], float] = partial(
potencia,
exponente=2,
)La anotación mejora el autocompletado y deja visible el contrato esperado.
Pruebas
Comprueba el comportamiento observable, no solo la creación del objeto.
def test_cuadrado():
assert cuadrado(4) == 16
assert cuadrado(-3) == 9Si la configuración fijada forma parte del requisito, también puedes verificar args y keywords.
Serialización y seguridad
Un partial puede serializarse en ciertos contextos si su función y sus argumentos son serializables. No uses esa posibilidad para cargar datos no confiables. Los formatos basados en pickle pueden ejecutar código al deserializar. Guarda configuración segura y reconstruye el partial en código confiable.
Rendimiento
El coste adicional de un partial suele ser pequeño. Prioriza la claridad y mide con timeit únicamente cuando el código esté en una ruta crítica real.
Errores frecuentes
- Fijar argumentos posicionales en el orden equivocado.
- Usar partial cuando un wrapper documentado sería más claro.
- Guardar contenedores mutables sin intención.
- Olvidar que las keywords pueden reemplazarse.
- Confundir
partialconpartialmethod. - Suponer que nombre y docstring se copian automáticamente.
- Crear demasiadas especializaciones y ocultar el flujo.
Buenas prácticas
- Elige nombres que describan la especialización.
- Prefiere keywords para configuraciones importantes.
- Mantén el partial cerca del lugar donde se usa.
- Evita argumentos mutables compartidos.
- Usa wrappers para APIs públicas y validación.
- Documenta callbacks que transportan contexto.
- Verifica la firma esperada por la biblioteca consumidora.
Conclusión
functools.partial en Python especializa funciones sin duplicar lógica. Reduce argumentos repetidos, adapta callbacks y hace explícita una configuración recurrente.
Úsalo cuando la transformación consista principalmente en fijar argumentos. Si necesitas validación, gestión de errores, documentación de dominio o lógica adicional, escribe una función wrapper normal. Con esta distinción, partial mejora la legibilidad sin ocultar el comportamiento del programa.






