Las aplicaciones y herramientas del ecosistema Apple usan archivos property list para guardar configuración, metadatos y estructuras sencillas. plistlib en Python lee y escribe plists XML y binarios sin depender de macOS, por lo que resulta útil en automatización de builds, análisis de preferencias, generación de configuración e interoperabilidad con iOS y macOS.
El formato admite diccionarios, listas, strings, números, booleanos, bytes y fechas, pero no es un serializador arbitrario de objetos Python. Las claves deben ser strings y los valores incompatibles generan errores. Esta guía explica la elección de formato, fechas aware, UIDs, validación de esquema, límites para entradas externas, comparación semántica y escritura atómica.
El contenido complementa nuestras guías sobre netrc, tempfile, filecmp, importlib.resources y copy.
Qué es un property list
Un plist representa un árbol de valores simples. La raíz suele ser un diccionario, aunque también pueden aparecer listas y otros tipos compatibles. Apple utiliza el formato para preferencias, manifests, metadatos de aplicaciones y archivos generados por herramientas.
Existen dos variantes principales: XML, legible y fácil de revisar, y binaria, más compacta y potencialmente más rápida.
Leer un archivo plist
import plistlib
with open("config.plist", "rb") as archivo:
datos = plistlib.load(archivo)
print(datos)Abre el archivo en modo binario. Con el valor predeterminado fmt=None, el módulo detecta automáticamente XML o binario.
Leer datos en memoria
contenido = ruta.read_bytes()
datos = plistlib.loads(contenido)loads() sirve para respuestas HTTP, entradas zip, recursos empaquetados, blobs de base de datos y fixtures. La entrada suele ser bytes; desde Python 3.13 puede ser string si se indica formato XML.
Escribir XML
config = {
"Nombre": "Mi Aplicación",
"Version": 3,
"Activo": True,
"Funciones": ["sync", "backup"],
}
with open("config.plist", "wb") as archivo:
plistlib.dump(config, archivo, fmt=plistlib.FMT_XML)XML es cómodo en repositorios, revisiones y diagnósticos. Sin embargo, los archivos grandes pueden ocupar bastante más que la versión binaria.
Escribir formato binario
with open("config-binario.plist", "wb") as archivo:
plistlib.dump(
config,
archivo,
fmt=plistlib.FMT_BINARY,
)El formato binario conserva el mismo modelo básico, pero no está pensado para edición manual. Utiliza herramientas adecuadas para inspeccionarlo.
Serializar a bytes
xml = plistlib.dumps(config, fmt=plistlib.FMT_XML)
binario = plistlib.dumps(config, fmt=plistlib.FMT_BINARY)Esta API facilita pruebas, servicios y bibliotecas que reciben buffers en lugar de archivos.
Tipos soportados
El módulo acepta strings, enteros, floats, booleanos, tuplas, listas, diccionarios con claves string, bytes, bytearray y objetos datetime. Los contenedores pueden combinar estos valores recursivamente.
Objetos personalizados, sets, Decimal, rutas y conexiones no se convierten automáticamente. Transfórmalos en una estructura declarativa.
Las claves deben ser strings
datos = {1: "valor"}
plistlib.dumps(datos) # TypeErrorCon skipkeys=True, las claves inválidas se omiten. Esto puede destruir información, por lo que el valor predeterminado False es más seguro.
Orden de claves
sort_keys=True es el valor predeterminado y genera una salida estable.
plistlib.dumps(datos, sort_keys=False)El XML ordenado reduce diffs ruidosos. Desactiva la ordenación solo si el orden de inserción tiene un valor documentado para humanos.
Fechas y zonas horarias
Los property lists representan fechas como instantes UTC. Desde Python 3.13, aware_datetime=True puede devolver valores con tzinfo=datetime.UTC y convertir fechas aware a UTC al escribir.
from datetime import datetime, UTC
config = {"GeneradoEn": datetime.now(UTC)}
contenido = plistlib.dumps(
config,
aware_datetime=True,
)Evita datetimes naive para instantes reales. Define una política y prueba cambios de horario de verano.
Cargar fechas aware
datos = plistlib.loads(
contenido,
aware_datetime=True,
)
print(datos["GeneradoEn"].tzinfo)Sin la opción, el comportamiento tradicional devuelve fechas naive. Mezclar ambos tipos provoca comparaciones incorrectas.
UIDs binarios
plistlib.UID representa identificadores usados por NSKeyedArchiver.
uid = plistlib.UID(42)
print(uid.data)El valor debe estar entre cero y 2**64 - 1. Un UID no es una referencia de objeto resuelta automáticamente.
NSKeyedArchiver no es un diccionario normal
Los archives almacenan tablas de objetos y referencias UID. Leer el plist es apenas el primer paso. No supongas que los campos equivalen directamente al modelo final.
En archives desconocidos limita tamaño, profundidad, número de objetos y tiempo de proceso.
Archivos inválidos
El contenido que no puede analizarse genera plistlib.InvalidFileException. El XML malformado también puede provocar excepciones del parser.
try:
datos = plistlib.loads(contenido)
except (plistlib.InvalidFileException, ValueError) as error:
raise RuntimeError("plist inválido") from errorNo incluyas todo el documento externo en el error, porque puede ser sensible o enorme.
Límites de enteros
Los plists binarios tienen límites de representación. Los valores fuera del rango generan OverflowError. Valida los intervalos en el modelo antes de serializar.
XML y seguridad
La implementación usa Expat. Aun así, los plists externos necesitan límites de tamaño y validación de aplicación. Nunca interpretes strings internos como rutas, comandos o código sin controles separados.
Los elementos desconocidos pueden ignorarse, así que un parsing exitoso no prueba que el documento cumpla tu esquema.
Validar el modelo
def validar_config(datos):
if not isinstance(datos, dict):
raise TypeError("la raíz debe ser un diccionario")
if not isinstance(datos.get("Nombre"), str):
raise ValueError("Nombre ausente o inválido")
if datos.get("Version", 0) < 1:
raise ValueError("Version inválida")Plistlib valida el formato, no las reglas de negocio. Comprueba campos obligatorios, tipos, rangos, enumeraciones y relaciones.
Escritura atómica
No sobrescribas directamente una configuración crítica. Crea un temporal en el mismo directorio, aplica permisos, serializa, sincroniza y reemplaza el destino.
from pathlib import Path
import tempfile
import os
final = Path("config.plist")
with tempfile.NamedTemporaryFile(
dir=final.parent,
delete=False,
) as tmp:
plistlib.dump(config, tmp)
tmp.flush()
os.fsync(tmp.fileno())
temporal = Path(tmp.name)
temporal.replace(final)Permisos y propietario
El reemplazo puede cambiar permisos, ACLs, atributos extendidos o propietario. Las configuraciones del sistema deben preservar metadatos o usar una herramienta de despliegue apropiada.
Comparación semántica
Comparar bytes puede mostrar diferencias causadas solo por whitespace, orden o formato. Para comparar datos, analiza ambos documentos.
a = plistlib.loads(archivo_a)
b = plistlib.loads(archivo_b)
assert a == bNormaliza fechas aware y naive antes de comparar.
Convertir XML a binario
with open("entrada.plist", "rb") as origen:
datos = plistlib.load(origen)
with open("salida.plist", "wb") as destino:
plistlib.dump(datos, destino, fmt=plistlib.FMT_BINARY)Valida entre lectura y escritura. Convertir no transforma un documento no confiable en seguro.
Templates empaquetados
Un template plist distribuido con un paquete puede leerse con importlib.resources, modificarse en memoria y escribirse en un directorio de configuración.
No escribas dentro del paquete instalado. Trata los recursos como solo lectura.
Pruebas
Crea pruebas de ida y vuelta para XML y binario, Unicode, bytes, colecciones vacías, fechas, enteros extremos, claves inválidas y documentos malformados.
def test_round_trip():
original = {"Nombre": "Café", "Activo": True}
for formato in (plistlib.FMT_XML, plistlib.FMT_BINARY):
restaurado = plistlib.loads(
plistlib.dumps(original, fmt=formato)
)
assert restaurado == originalCompatibilidad
Si una versión antigua de macOS, iOS, Swift, Objective-C u otra herramienta consume el archivo, prueba el artefacto final en la plataforma real.
Errores frecuentes
- Abrir archivos en modo texto.
- Usar claves no string.
- Activar
skipkeysy perder datos. - Mezclar fechas aware y naive.
- Tratar UID como objeto resuelto.
- Confiar en el archivo solo porque pudo analizarse.
- Sobrescribir configuración crítica directamente.
- Comparar bytes cuando se quiere comparar datos.
Buenas prácticas
- Abre archivos en modo binario.
- Valida el modelo después de cargar.
- Usa
aware_datetime=Truepara instantes. - Elige XML para revisión y binario con una razón.
- Escribe de forma atómica.
- Limita tamaño y profundidad externos.
- Prueba ambos formatos utilizados.
- Nunca ejecutes strings extraídos del plist.
Conclusión
plistlib en Python soporta property lists XML y binarios, incluidos bytes, fechas y UIDs. Es una herramienta práctica para interoperar con el ecosistema Apple y automatizar tareas multiplataforma.
El parser valida el formato, no las reglas de tu aplicación. Combínalo con límites, esquema, política de timezone y escritura atómica. Consulta la documentación oficial de plistlib y la documentación de property lists de Apple.







