El módulo configparser en Python lee y escribe archivos de configuración con estilo INI, organizados en secciones, opciones y valores de texto. Es apropiado para aplicaciones de escritorio, herramientas de línea de comandos, scripts administrativos y programas que necesitan ajustes editables por personas sin recurrir a JSON, YAML o una base de datos.
INI no tiene una única especificación universal. Las aplicaciones difieren en reglas para comentarios, mayúsculas, delimitadores, opciones vacías, valores multilínea e interpolación. Configura el parser deliberadamente y documenta el dialecto aceptado.
Crea un archivo INI básico
[servidor]
host = 127.0.0.1
puerto = 8080
debug = false
[logs]
nivel = INFO
archivo = logs/app.logCada encabezado entre corchetes define una sección. Las opciones utilizan = o : como delimitadores por defecto.
Lee la configuración
import configparser
config = configparser.ConfigParser()
leidos = config.read("app.ini", encoding="utf-8")
if not leidos:
raise FileNotFoundError("app.ini no fue cargado")
host = config["servidor"]["host"]
puerto = config["servidor"].getint("puerto")
debug = config["servidor"].getboolean("debug")read() ignora silenciosamente archivos que no puede abrir y devuelve la lista de nombres procesados. Comprueba el resultado para archivos obligatorios o usa read_file() con un handle abierto.
Todos los valores son strings
El parser no infiere tipos. Usa getint(), getfloat() y getboolean().
timeout = config["servidor"].getfloat("timeout", fallback=5.0)
workers = config["servidor"].getint("workers", fallback=4)
activo = config["servidor"].getboolean("activo", fallback=True)getboolean() reconoce yes/no, true/false, on/off y 1/0. No uses bool("false"), porque toda string no vacía es verdadera.
Usa fallback correctamente
En el proxy de sección, el segundo argumento de get() es el fallback:
nivel = config["logs"].get("nivel", "INFO")En la API del parser, utiliza la palabra clave:
nivel = config.get("logs", "nivel", fallback="INFO")Los valores heredados de DEFAULT tienen prioridad sobre el fallback, incluso cuando la opción no aparece físicamente en la sección.
Define valores predeterminados
[DEFAULT]
timeout = 10
reintentos = 3
[api]
url = https://api.example.com
[worker]
reintentos = 5Las opciones de DEFAULT son visibles en todas las secciones. Eliminar una sobrescritura hace reaparecer el valor predeterminado.
Combina archivos por capas
Los archivos leídos más tarde sobrescriben las opciones conflictivas y conservan las demás.
config.read(
["defaults.ini", "sitio.ini", "usuario.ini"],
encoding="utf-8",
)Un diseño común utiliza defaults versionados, configuración del servidor y overrides personales. Después de cargar todas las capas, valida las opciones obligatorias.
Usa read_file para archivos obligatorios
from pathlib import Path
ruta = Path("defaults.ini")
with ruta.open(encoding="utf-8") as archivo:
config.read_file(archivo, source=str(ruta))Los errores de apertura y sintaxis se propagan. El parámetro source mejora los diagnósticos.
Lee strings y diccionarios
read_string() es útil en tests y configuraciones embebidas controladas.
config.read_string("""
[servicio]
url = https://example.com
""", source="defaults embebidos")read_dict() carga mappings anidados y convierte claves y valores a texto.
config.read_dict({
"servidor": {"puerto": 8080, "debug": False},
})Mayúsculas y minúsculas
Los nombres de sección distinguen mayúsculas, pero las opciones no. optionxform() convierte las claves a minúsculas.
config = configparser.ConfigParser()
config.read_string("""
[Seccion]
MiClave = valor
""")
print(list(config["Seccion"]))
# ['miclave']Para conservar la forma original:
config.optionxform = strConfigúralo antes de leer. Una transformación personalizada debe ser idempotente.
Interpolación básica
ConfigParser usa BasicInterpolation por defecto.
[rutas]
base = /opt/miapp
logs = %(base)s/logs
cache = %(base)s/cacheEscribe %% para un porcentaje literal. La sustitución ocurre al obtener el valor.
Interpolación extendida
ExtendedInterpolation utiliza ${sección:opción} y permite referencias entre secciones.
from configparser import ConfigParser, ExtendedInterpolation
config = ConfigParser(interpolation=ExtendedInterpolation())[comun]
raiz = /srv/app
[logs]
dir = ${comun:raiz}/logsUsa $$ para un cifrón literal. Referencias ausentes y ciclos generan excepciones.
Desactiva la interpolación cuando sea necesario
Contraseñas, templates, expresiones regulares y fragmentos shell pueden contener % o $. Si el formato no utiliza sustitución:
config = configparser.ConfigParser(interpolation=None)En una lectura puntual, usa raw=True.
Añade conversores personalizados
El argumento converters crea nuevos métodos get*.
from pathlib import Path
config = configparser.ConfigParser(
converters={
"path": Path,
"lista": lambda valor: [x.strip() for x in valor.split(",")],
},
)
ruta_log = config["logs"].getpath("archivo")
origines = config["cors"].getlista("origines")Convertir no equivale a validar. Comprueba rangos, extensiones, raíces permitidas, existencia y reglas de negocio.
Personaliza valores booleanos
config.BOOLEAN_STATES = {
"habilitado": True,
"deshabilitado": False,
}Un vocabulario localizado puede mejorar legibilidad, pero reduce portabilidad. Documenta las formas aceptadas.
Opciones sin valor
Algunos dialectos usan flags sin delimitador.
config = configparser.ConfigParser(allow_no_value=True)[recursos]
modo_seguro
auditoriaEstas opciones devuelven None. En Python reciente, continuar una opción sin valor con una línea indentada genera MultilineContinuationError.
Secciones sin nombre
Python 3.13 añadió soporte opcional para opciones antes del primer encabezado.
config = configparser.ConfigParser(allow_unnamed_section=True)
config.read_string("""
clave = valor
[otra]
x = 1
""")
valor = config[configparser.UNNAMED_SECTION]["clave"]Actívalo únicamente para compatibilidad con un formato existente.
Comentarios y valores multilínea
Por defecto, # y ; introducen comentarios en líneas propias. Los comentarios inline están deshabilitados porque no existe un escape fiable.
Los valores multilínea deben tener mayor indentación:
[mensaje]
texto = primera línea
segunda línea
tercera líneaPara archivos editados por personas, considera empty_lines_in_values=False para reducir ambigüedad.
Mantén strict activo
strict=True, valor predeterminado actual, rechaza secciones y opciones duplicadas dentro de una fuente. Detecta errores de escritura y colisiones causadas por la normalización de caja.
Trata errores con claridad
try:
with open("app.ini", encoding="utf-8") as archivo:
config.read_file(archivo)
except configparser.MissingSectionHeaderError as error:
raise RuntimeError("Falta un encabezado de sección") from error
except configparser.DuplicateOptionError as error:
raise RuntimeError("Opción duplicada") from error
except configparser.InterpolationError as error:
raise RuntimeError("Interpolación inválida") from errorIncluye fuente y línea en el diagnóstico, pero nunca registres valores secretos.
Escribe configuración
from pathlib import Path
config["servidor"]["puerto"] = "9090"
with Path("app.ini").open("w", encoding="utf-8") as archivo:
config.write(archivo)Todos los valores deben ser strings. En Python 3.14, write() genera InvalidWriteError cuando la representación no podría leerse correctamente.
Escribe de forma atómica
No sobrescribas directamente un archivo crítico. Escribe en un temporal del mismo directorio, sincroniza y sustituye el destino.
import os
from pathlib import Path
from tempfile import NamedTemporaryFile
destino = Path("app.ini")
with NamedTemporaryFile("w", encoding="utf-8", dir=destino.parent, delete=False) as temporal:
config.write(temporal)
temporal.flush()
os.fsync(temporal.fileno())
ruta_temporal = temporal.name
os.replace(ruta_temporal, destino)Controla permisos y usa locking o un servicio central cuando existan varios escritores.
Los comentarios no se conservan
Leer y escribir nuevamente elimina comentarios y formato original. Si los usuarios mantienen explicaciones importantes, evita reescrituras automáticas o utiliza una biblioteca que preserve el layout.
No guardes secretos en INI
Los archivos INI son texto plano. Contraseñas, tokens y claves privadas deben proceder de un gestor de secretos, variable de entorno protegida o almacén del sistema. Guarda solo el identificador del secreto.
La guía de site en Python explica rutas y entornos; importlib.resources permite distribuir defaults dentro del paquete; y fileinput en Python ayuda con múltiples archivos.
Valida después de leer
def cargar_servidor(config):
puerto = config["servidor"].getint("puerto")
if not 1 <= puerto <= 65535:
raise ValueError("puerto fuera de rango")
host = config["servidor"].get("host", "127.0.0.1").strip()
if not host:
raise ValueError("host vacío")
return host, puertoEl parser valida estructura y conversiones básicas; la aplicación debe validar semántica.
ConfigParser, TOML y JSON
INI es amigable para configuraciones pequeñas. TOML posee especificación más estricta y tipos nativos. JSON es interoperable, pero no acepta comentarios. Elige según complejidad, ecosistema y necesidad de edición humana.
Buenas prácticas
- Abre archivos explícitamente como UTF-8.
- Mantén
strict=True. - Comprueba archivos obligatorios.
- Define el comportamiento de mayúsculas.
- Desactiva interpolación si no la utilizas.
- Valida cada valor convertido.
- Escribe actualizaciones de forma atómica.
- Mantén secretos fuera del INI.
Conclusión
configparser en Python proporciona una interfaz madura para configuración INI con secciones, defaults, capas, interpolación, conversores y acceso como mapping. Funciona mejor cuando el dialecto aceptado es simple y está documentado.
Consulta la documentación oficial de configparser y la documentación de tomllib.







