Aplicativos e ferramentas do ecossistema Apple usam arquivos property list para armazenar configurações, metadados e estruturas simples. O módulo plistlib no Python lê e grava plists XML e binários sem depender do macOS, permitindo automatizar builds, analisar preferências, gerar arquivos de configuração e interoperar com ferramentas de iOS e macOS.
O formato aceita dicionários, listas, strings, números, booleanos, bytes e datas, mas não é uma serialização arbitrária de objetos Python. Chaves devem ser strings e valores fora do conjunto suportado geram erros. Neste guia você aprenderá a escolher o formato, tratar datas, validar entradas, usar arquivos temporários e evitar riscos ao processar plists externos.
O conteúdo complementa nossos artigos sobre netrc, tempfile, filecmp, importlib.resources e copy.
O que é um property list
Um plist representa uma árvore de valores simples. O objeto raiz costuma ser um dicionário, mas listas e outros tipos compatíveis também podem aparecer. O formato é usado por preferências, manifests, metadados de aplicativos e arquivos produzidos por ferramentas Apple.
Existem duas variantes principais: XML, legível e fácil de comparar, e binária, mais compacta e rápida para algumas cargas.
Ler um arquivo plist
import plistlib
with open("config.plist", "rb") as arquivo:
dados = plistlib.load(arquivo)
print(dados)Abra o arquivo em modo binário. Quando fmt=None, que é o padrão, o módulo tenta detectar automaticamente se o conteúdo é XML ou binário.
Ler bytes em memória
conteudo = caminho.read_bytes()
dados = plistlib.loads(conteudo)loads() é útil quando o conteúdo vem de uma resposta HTTP, um arquivo zip, um recurso empacotado ou um banco de dados. A entrada normalmente é bytes; no Python 3.13, strings também podem ser aceitas quando o formato XML é indicado.
Gravar um arquivo XML
config = {
"Nome": "Meu Aplicativo",
"Versao": 3,
"Ativo": True,
"Recursos": ["sincronizacao", "backup"],
}
with open("config.plist", "wb") as arquivo:
plistlib.dump(config, arquivo, fmt=plistlib.FMT_XML)XML é conveniente em repositórios, revisões de código e diagnósticos. Entretanto, arquivos grandes podem ocupar mais espaço do que a representação binária.
Gravar formato binário
with open("config-binary.plist", "wb") as arquivo:
plistlib.dump(
config,
arquivo,
fmt=plistlib.FMT_BINARY,
)O formato binário preserva os mesmos tipos básicos, mas não é apropriado para edição manual. Use ferramentas específicas para inspeção quando necessário.
Serializar para bytes
xml = plistlib.dumps(config, fmt=plistlib.FMT_XML)
binario = plistlib.dumps(config, fmt=plistlib.FMT_BINARY)Essa API facilita testes, respostas de serviços e integração com bibliotecas que aceitam buffers em memória.
Tipos suportados
O módulo aceita strings, inteiros, floats, booleanos, listas, tuplas, dicionários com chaves string, bytes, bytearray e objetos datetime. Estruturas podem combinar esses valores recursivamente.
Objetos personalizados, sets, Decimal e caminhos não são convertidos automaticamente. Transforme-os em uma estrutura declarativa antes da serialização.
Chaves precisam ser strings
dados = {1: "valor"}
plistlib.dumps(dados) # TypeErrorCom skipkeys=True, chaves inválidas são ignoradas. Essa opção pode causar perda silenciosa de dados, portanto o padrão False é mais seguro.
Valide o modelo antes de escrever e converta chaves apenas quando a transformação estiver definida pelo contrato.
Ordenação de chaves
sort_keys=True é o padrão e produz dicionários em ordem de chave. Isso ajuda a criar XML estável e diffs menores.
plistlib.dumps(dados, sort_keys=False)Desabilite a ordenação quando a ordem de inserção tiver valor para ferramentas humanas. Consumidores não devem depender da ordem sem uma especificação explícita.
Datas e timezone
Property lists representam datas em UTC. Desde Python 3.13, aware_datetime=True permite ler objetos com tzinfo=datetime.UTC e converter datas aware para UTC durante a gravação.
from datetime import datetime, UTC
config = {"GeradoEm": datetime.now(UTC)}
conteudo = plistlib.dumps(
config,
aware_datetime=True,
)Evite datetimes ingênuos quando o valor representa um instante real. Defina uma política explícita e teste a conversão ao atravessar fusos horários.
Datas ao carregar
dados = plistlib.loads(
conteudo,
aware_datetime=True,
)
print(dados["GeradoEm"].tzinfo)Sem essa opção, o comportamento tradicional produz datas sem timezone. Misturar os dois modos pode gerar comparações incorretas.
UIDs de plists binários
plistlib.UID representa identificadores usados por dados do NSKeyedArchiver.
uid = plistlib.UID(42)
print(uid.data)O valor deve ficar entre zero e 2**64 - 1. Um UID não é uma referência resolvida automaticamente; interpretar um archive completo exige conhecer a estrutura do formato produtor.
Não trate NSKeyedArchiver como dicionário comum
Plists de NSKeyedArchiver armazenam tabelas de objetos e referências por UID. Ler o plist é apenas a primeira etapa. Não assuma que os campos representam diretamente o modelo final.
Ao analisar archives desconhecidos, limite tamanho, profundidade e quantidade de objetos.
Erros de arquivo inválido
Conteúdo que não pode ser analisado gera plistlib.InvalidFileException. XML malformado também pode provocar exceções do parser Expat.
try:
dados = plistlib.loads(conteudo)
except (plistlib.InvalidFileException, ValueError) as erro:
raise RuntimeError("plist inválido") from erroNão mostre todo o conteúdo externo em mensagens de erro, pois ele pode conter dados sensíveis ou muito grandes.
Inteiros fora do intervalo
O formato binário possui limites para inteiros. Valores não representáveis geram OverflowError.
Valide intervalos na camada de modelo e não dependa apenas da exceção do serializer.
XML e segurança
O parser usa Expat para XML. Ainda assim, trate plists externos como entrada não confiável. Estabeleça limites de tamanho antes de carregar e não interprete strings internas como caminhos, comandos ou código.
Elementos desconhecidos podem ser ignorados pelo parser de plist, portanto uma leitura bem-sucedida não confirma conformidade com o esquema da sua aplicação.
Validar o modelo
def validar_config(dados):
if not isinstance(dados, dict):
raise TypeError("a raiz deve ser um dicionário")
if not isinstance(dados.get("Nome"), str):
raise ValueError("Nome ausente ou inválido")
if dados.get("Versao", 0) < 1:
raise ValueError("Versao inválida")O módulo garante a sintaxe do plist, não as regras de negócio. Valide campos obrigatórios, tipos, limites, enumerações e relações.
Escrever de forma atômica
Não grave diretamente sobre um arquivo crítico. Crie um temporário no mesmo diretório, aplique permissões, serialize, faça flush e substitua o 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())
temporario = Path(tmp.name)
temporario.replace(final)Preservar permissões e proprietário
A substituição pode alterar permissões, ACLs ou proprietário. Em arquivos do sistema, capture e reaplique metadados conforme a política, ou utilize uma ferramenta de deploy apropriada.
Comparar plists semanticamente
Comparar bytes pode indicar diferença apenas por formato, ordem ou whitespace. Para comparação semântica, carregue ambos os arquivos e compare as estruturas Python.
a = plistlib.loads(arquivo_a)
b = plistlib.loads(arquivo_b)
assert a == bDatas aware e ingênuas precisam ser normalizadas antes da comparação.
Converter XML para binário
with open("entrada.plist", "rb") as origem:
dados = plistlib.load(origem)
with open("saida.plist", "wb") as destino:
plistlib.dump(dados, destino, fmt=plistlib.FMT_BINARY)Valide o modelo entre leitura e escrita. A conversão não deve ser usada para “limpar” arquivos que você ainda não confia.
Recursos empacotados
Templates plist distribuídos com um pacote podem ser lidos por importlib.resources, modificados em memória e gravados em um local apropriado do usuário.
Não tente escrever dentro do pacote instalado. Trate recursos empacotados como somente leitura.
Testes
Crie testes de ida e volta para XML e binário, datas, bytes, listas vazias, Unicode, inteiros extremos, chaves inválidas e arquivos malformados.
def test_round_trip():
original = {"Nome": "Café", "Ativo": True}
for formato in (plistlib.FMT_XML, plistlib.FMT_BINARY):
restaurado = plistlib.loads(
plistlib.dumps(original, fmt=formato)
)
assert restaurado == originalCompatibilidade
Se o arquivo será consumido por versões antigas de macOS, iOS ou outra linguagem, teste o artefato final na plataforma real. Nem toda ferramenta interpreta extensões ou estruturas específicas da mesma maneira.
Erros frequentes
- Abrir o arquivo em modo texto.
- Usar chaves que não são strings.
- Ativar
skipkeyse perder campos silenciosamente. - Misturar datetimes aware e ingênuos.
- Tratar UID como objeto já resolvido.
- Confiar em um plist apenas porque o parsing funcionou.
- Gravar diretamente sobre configuração crítica.
- Comparar bytes quando o objetivo é comparar dados.
Boas práticas
- Abra arquivos em modo binário.
- Valide o modelo após carregar.
- Prefira
aware_datetime=Truepara instantes. - Use XML para revisão e binário quando houver motivo.
- Escreva de forma atômica.
- Limite tamanho e profundidade de entradas externas.
- Teste ida e volta em todos os formatos usados.
- Não execute strings extraídas do arquivo.
Conclusão
O módulo plistlib no Python oferece suporte completo às variantes XML e binária de property lists, incluindo bytes, datas e UIDs. Ele é uma ferramenta prática para automação e interoperabilidade com o ecossistema Apple.
O parser valida o formato, não as regras da aplicação. Combine-o com limites, validação de esquema, política de timezone e escrita atômica. Consulte a documentação oficial de plistlib e a documentação de property lists da Apple para detalhes de compatibilidade.







