plistlib en Python: archivos plist

Publicado el: 08/08/2026
Tempo de leitura: 6 minutos
Icono de configuración que representa archivos plist con plistlib en Python

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)  # TypeError

Con 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 error

No 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 == b

Normaliza 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 == original

Compatibilidad

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 skipkeys y 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=True para 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Candado digital que representa credenciales por host con netrc en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    netrc en Python: credenciales por host

    Aprende netrc en Python para leer credenciales por host, validar permisos, tratar errores e integrar clientes de red de forma

    Ler mais

    Tempo de leitura: 7 minutos
    08/08/2026
    Mensaje digital que representa codificación quoted-printable con quopri en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    quopri en Python: quoted-printable

    Aprende quopri en Python para codificar y decodificar quoted-printable en correo, archivos e integraciones MIME de forma segura.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Icono de archivo digital que representa tipos MIME con mimetypes en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    mimetypes en Python: tipos MIME

    Aprende mimetypes en Python para identificar tipos MIME, extensiones y encodings de forma segura en cargas, descargas, correo y APIs

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Búsqueda binaria y listas ordenadas con bisect en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    bisect en Python: listas ordenadas

    Aprende bisect en Python para búsqueda binaria, inserción ordenada, duplicados, rangos y diseño seguro de listas.

    Ler mais

    Tempo de leitura: 5 minutos
    08/08/2026
    Código y archivos empaquetados con importlib.resources en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    importlib.resources en Python: guía práctica

    Aprende importlib.resources en Python para acceder a archivos empaquetados con seguridad en wheels y aplicaciones instaladas.

    Ler mais

    Tempo de leitura: 5 minutos
    07/08/2026
    Teclado y flujo de datos que representa el procesamiento de varios archivos con fileinput en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    fileinput en Python: lee varios archivos

    Aprende fileinput en Python para leer varios archivos o stdin, rastrear líneas, abrir archivos comprimidos y reescribir contenido con backups.

    Ler mais

    Tempo de leitura: 7 minutos
    07/08/2026