Serialização de Dados em Python: Pickle, Shelve, JSON e ConfigParser

Este artigo explora a serialização de dados em Python, focando nos módulos pickle, shelve, json e configparser. A serialização é o processo de converter um objeto Python em um formato que pode ser armazenado ou transmitido, e posteriormente reconstruído. Diferentes métodos de serialização produzem resultados distintos, mas todos visam facilitar o armazenamento e a transmissão de dados.

  1. O que é Serialização?

A serialização é o processo de transformar estruturas de dados ou estado de objeto em um formato que pode ser armazenado (por exemplo, em um arquivo ou banco de dados) ou transmitido (por exemplo, através de uma rede) e reconstruído posteriormente em um ambiente de execução idêntico ou compatível. Em Python, existem várias abordagens para isso:

  • pickle: Permite converter quase qualquer tipo de dado Python em uma sequência de bytes (bytes) para armazenamento ou transmissão. O processo inverso, a desserialização, reconstrói o objeto Python original a partir dos bytes.
  • shelve: Oferece uma forma de persistência de dados, semelhante a um banco de dados pequeno e simples. Ele permite armazenar objetos Python de forma que possam ser acessados posteriormente, comportando-se de maneira similar a um dicionário.
  • json: Converte tipos de dados Python comuns (como dicionários e listas) em strings no formato JSON (JavaScript Object Notation). JSON é amplamente utilizado para intercâmbio de dados entre sistemas, especialmente em aplicações web.
  1. Módulo pickle

O módulo pickle é usado para serializar e desserializar objetos Python. Ele converte objetos Python em um fluxo de bytes (bytes) para armazenamento ou transmissão. O formato resultante não é legível por humanos.

As principais funções incluem:

  • pickle.dumps(obj): Serializa um objeto obj em uma string de bytes.
  • pickle.loads(bytes_obj): Desserializa uma string de bytes bytes_obj de volta para um objeto Python.
  • pickle.dump(obj, file): Serializa um objeto obj e o escreve diretamente em um arquivo aberto em modo binário.
  • pickle.load(file): Lê um fluxo de bytes de um arquivo aberto em modo binário e o desserializa de volta para um objeto Python.

Exemplo com uma classe customizada:


import pickle

class Animal:
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade

    def emitir_som(self):
        print(f"{self.nome} faz um som.")

cachorro = Animal("Rex", 3)

# Serializando o objeto para bytes
dados_serializados = pickle.dumps(cachorro)
print(f"Bytes serializados: {dados_serializados}")

# Desserializando os bytes de volta para um objeto
cachorro_desserializado = pickle.loads(dados_serializados)
print(f"Objeto desserializado: Nome={cachorro_desserializado.nome}, Idade={cachorro_desserializado.idade}")
cachorro_desserializado.emitir_som()

Para persistir em arquivos:


import pickle

class Cachorro:
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade

    def latir(self):
        print(f"{self.nome} late!")

meu_cachorro = Cachorro("Buddy", 5)

# Escrevendo o objeto em um arquivo (modo binário 'wb')
with open("cachorro.pkl", "wb") as f:
    pickle.dump(meu_cachorro, f)

# Lendo o objeto de volta do arquivo (modo binário 'rb')
with open("cachorro.pkl", "rb") as f:
    cachorro_lido = pickle.load(f)
    cachorro_lido.latir()

É possível serializar múltiplos objetos, mas geralmente é mais prático serializar uma coleção (como uma lista) que contém esses objetos:


import pickle

class Gato:
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade

    def miar(self):
        print(f"{self.nome} mia.")

gatos = [Gato("Felix", 2), Gato("Whiskers", 4)]

# Serializando uma lista de objetos
with open("gatos.pkl", "wb") as f:
    pickle.dump(gatos, f)

# Desserializando a lista de objetos
with open("gatos.pkl", "rb") as f:
    gatos_lidos = pickle.load(f)
    for gato in gatos_lidos:
        gato.miar()

# Alternativamente, pode-se ler múltiplos objetos sequencialmente,
# mas é preciso tratar o fim do arquivo (EOFError).
# É mais robusto serializar/desserializar coleções.
# Exemplo de leitura sequencial (menos recomendado para múltiplos objetos):
# with open("gatos.pkl", "rb") as f:
#     while True:
#         try:
#             obj = pickle.load(f)
#             # Processar obj
#         except EOFError:
#             break

É importante notar que o formato pickle é específico do Python e não é seguro usá-lo com dados de fontes não confiáveis, pois um arquivo pickle malicioso pode executar código arbitrário durante a desserialização.

  1. Módulo shelve

O módulo shelve oferece um mecanismo de persistência simples, atuando como um "banco de dados" para objetos Python. Ele se comporta de forma muito parecida com um dicionário.

Exemplo de uso:


import shelve

# Abrindo ou criando um arquivo de shelf
# O arquivo será criado com extensões como .bak, .dat, .dir dependendo do backend
with shelve.open("meu_shelf") as s:
    # Armazenando dados (objetos devem ser serializáveis por pickle)
    s["usuario_id_1"] = {"nome": "Alice", "email": "alice@example.com"}
    s["configuracoes"] = {"tema": "escuro", "idioma": "pt-br"}
    s["lista_numeros"] = [1, 2, 3, 4, 5]

# Acessando os dados
with shelve.open("meu_shelf") as s:
    print(s["usuario_id_1"])
    print(s["configuracoes"]["tema"])
    print(s["lista_numeros"])

# Modificando dados aninhados - atenção com writeback=True
with shelve.open("meu_shelf", writeback=True) as s:
    # Se você modificar um objeto mutável (como um dicionário) que foi armazenado,
    # a modificação não é automaticamente persistida sem writeback=True.
    # Com writeback=True, as modificações em objetos mutáveis são salvas.
    s["usuario_id_1"]["email"] = "alice.nova@example.com"
    print("Email atualizado.")

with shelve.open("meu_shelf") as s:
    print(s["usuario_id_1"]) # O email foi atualizado se writeback=True foi usado

# Deletando itens
with shelve.open("meu_shelf", writeback=True) as s:
    if "configuracoes" in s:
        del s["configuracoes"]
        print("Configurações deletadas.")

with shelve.open("meu_shelf") as s:
    if "configuracoes" not in s:
        print("Configurações não encontradas.")

# Iterando sobre as chaves
with shelve.open("meu_shelf") as s:
    print("Chaves no shelf:")
    for key in s:
        print(key)
    
    print("Itens no shelf:")
    for key, value in s.items():
        print(f"{key}: {value}")


A opção writeback=True é importante quando se manipulam objetos mutáveis (como listas e dicionários) que foram armazenados. Sem ela, as modificações nesses objetos não são automaticamente salvas no arquivo. No entanto, writeback=True pode consumir mais memória, pois mantém objetos modificados na memória.

  1. Módulo json

O módulo json é fundamental para a comunicação entre diferentes sistemas, especialmente em aplicações web. Ele lida com a conversão de tipos de dados Python para o formato JSON e vice-versa.

JSON (JavaScript Object Notation) é um formato leve de intercâmbio de dados, fácil para humanos lerem e escreverem, e fácil para máquinas analisarem e gerarem. Sua estrutura é baseada em pares chave-valor e listas ordenadsa, similar aos dicionários e listas do Python.

Funções principais:

  • json.dumps(obj, ensure_ascii=True, indent=None): Serializa um objeto Python obj em uma string JSON.
    • ensure_ascii=False: Permite que caracteres não-ASCII (como acentos e caracteres de outras línguas) sejam representados diretamente em vez de usarem sequências de escape Unicode (\uXXXX).
    • indent: Se especificado (por exemplo, indent=4), formata a string JSON de saída com indentação para melhor legibilidade.
  • json.loads(json_string): Desserializa uma string JSON json_string para um objeto Python.
  • json.dump(obj, file, ensure_ascii=False, indent=None): Serializa um objeto Python obj e o escreve diretamente em um arquivo (file) como JSON.
  • json.load(file): Lê uma string JSON de um arquivo (file) e a desserializa para um objeto Python.

Exemplo de serialização e desserialização:


import json

dados_python = {
    "nome": "Fernando",
    "idade": 30,
    "ativo": True,
    "hobbies": ["ler", "cozinhar", "programar"],
    "endereco": {
        "rua": "Rua Principal",
        "numero": 123,
        "cidade": "Exemploville"
    }
}

# Serializando para string JSON (com caracteres não-ASCII e indentação)
json_string = json.dumps(dados_python, ensure_ascii=False, indent=4)
print("JSON String:")
print(json_string)

# Desserializando a string JSON de volta para um objeto Python
dados_desserializados = json.loads(json_string)
print("\nObjeto Python desserializado:")
print(type(dados_desserializados))
print(dados_desserializados["nome"])
print(dados_desserializados["endereco"]["cidade"])

Persistindo JSON em arquivos:


import json

dados_para_salvar = {"produto": "Notebook", "preco": 4500.50, "estoque": 15}

# Escrevendo em um arquivo JSON
with open("produto.json", "w", encoding="utf-8") as f:
    json.dump(dados_para_salvar, f, ensure_ascii=False, indent=2)

# Lendo de um arquivo JSON
with open("produto.json", "r", encoding="utf-8") as f:
    dados_lidos = json.load(f)
    print("\nDados lidos do arquivo JSON:")
    print(dados_lidos)

Importante: Ao escrever múltiplos objetos JSON em um único arquivo, cada objeto deve ser escrito em sua própria linha. O json.dump() em loop em um arquivo sem tratamento especial resultará em um arquivo com JSONs concatenados, o que não é um JSON válido e não pode ser lido com json.load(). A abordagem correta é escrever cada JSON em uma linha separada, usando json.dumps() e adicionando uma quebra de linha manual, ou armazenar os objetos em uma lista e usar json.dump() uma única vez.


import json

lista_de_objetos = [{"id": 1, "nome": "Item A"}, {"id": 2, "nome": "Item B"}]

# Método 1: Serializar uma lista inteira (resulta em um único JSON array)
with open("itens_array.json", "w", encoding="utf-8") as f:
    json.dump(lista_de_objetos, f, ensure_ascii=False, indent=2)

# Método 2: Escrever cada objeto em uma linha separada (formato JSON Lines)
with open("itens_lines.json", "w", encoding="utf-8") as f:
    for item in lista_de_objetos:
        # Usa dumps para converter o objeto em string e adiciona '\n'
        json_line = json.dumps(item, ensure_ascii=False) + "\n"
        f.write(json_line)

# Lendo do formato JSON Lines
print("\nLendo do formato JSON Lines:")
with open("itens_lines.json", "r", encoding="utf-8") as f:
    for line in f:
        if line.strip(): # Ignora linhas em branco
            item_lido = json.loads(line.strip())
            print(item_lido)

  1. Módulo configparser

O módulo configparser é usado para ler e escrever arquivos de configuração no formato INI, que são estruturados em seções e pares chave-valor.

Um arquivo INI típico se parece com:


[DEFAULT]
ServerAliveInterval = 45
Compression = yes

[Seção1]
chave1 = valor1
chave2 = valor2

[Seção2]
chave_a = valor_a
chave_b = valor_b

O configparser permite gerenciar essas configurações de forma programática.

Exemplo de criação e escrita de um arquivo INI:


import configparser

# Cria um objeto ConfigParser
config = configparser.ConfigParser()

# Define seções e valores
config['DEFAULT'] = {
    'Servidor': 'localhost',
    'Porta': '8080'
}

config['BancoDados'] = {
    'tipo': 'PostgreSQL',
    'host': 'db.example.com',
    'usuario': 'admin',
    'senha': 'password123'
}

config['API'] = {
    'url_base': 'https://api.example.com/v1',
    'timeout_segundos': '30'
}

# Escreve o conteúdo em um arquivo .ini
nome_arquivo_config = "config.ini"
with open(nome_arquivo_config, "w", encoding="utf-8") as f:
    config.write(f)

print(f"Arquivo de configuração '{nome_arquivo_config}' criado.")

Exemplo de leitura de um arquivo INI:


import configparser

nome_arquivo_config = "config.ini"
config = configparser.ConfigParser()

# Lê o arquivo de configuração
config.read(nome_arquivo_config)

# Obtém todas as seções (excluindo DEFAULT)
print("Seções:", config.sections())

# Acessa valores - note que os valores são lidos como strings por padrão
print("\nConfigurações do Banco de Dados:")
print("Tipo:", config.get('BancoDados', 'tipo'))
print("Host:", config.get('BancoDados', 'host'))
print("Usuário:", config.get('BancoDados', 'usuario'))

# Acesso como dicionário (mais conveniente)
print("\nConfigurações da API:")
print("URL Base:", config['API']['url_base'])
# Para obter valores como tipos específicos (int, float, bool), use getint, getfloat, getboolean
timeout = config.getint('API', 'timeout_segundos')
print("Timeout (int):", timeout)
print("Tipo do Timeout:", type(timeout))

# Iterando sobre as opções de uma seção
print("\nOpções da Seção BancoDados:")
for chave in config['BancoDados']:
    print(f"- {chave}: {config['BancoDados'][chave]}")

# Modificando e salvando
print("\nModificando e salvando...")
config.set('BancoDados', 'porta', '5432') # Adiciona ou modifica uma opção
config.add_section('Log')                 # Adiciona uma nova seção
config['Log']['nivel'] = 'INFO'
config['Log']['arquivo'] = '/var/log/app.log'

# Removendo uma seção ou opção
if 'DEFAULT' in config:
    config.remove_section('DEFAULT')
    print("Seção DEFAULT removida.")
if config.has_option('API', 'timeout_segundos'):
    config.remove_option('API', 'timeout_segundos')
    print("Opção 'timeout_segundos' da seção API removida.")


# Salva as alterações de volta no arquivo
with open(nome_arquivo_config, "w", encoding="utf-8") as f:
    config.write(f)

print(f"Arquivo de configuração '{nome_arquivo_config}' atualizado.")

O configparser é uma ferramenta útil para gerenciar configurações de aplicações de forma organizada e legível.

Tags: pickle shelve JSON configparser serialização

Publicado em 10-5 11:43