Conheça o Plugfeed | » Início » Programação » Python e Zope » Serialização
Conheça o Plugfeed | » Início » Programação » Python e Zope » Serialização -->
 
Avaliação: Não avaliado | Publicado em: 18/02/2008
Serialização
Fred Jader Desenvolvedor web desde os 12 anos, Frederico Jader hoje em dia atua como diretor de arte e administrador de portais em geral, tendo como mais novo projeto o site www.gamesnahora.com
Serialização

O módulo Pickle faz a serialização e deserialização de objetos Python. Isso significa poder armazenar objetos em arquivos e ter como recuperá-los (transformando arquivos em objetos novamente). Praticamente qualquer objeto Python pode ser serializado, como listas, dicionários, números (inteiros, longos, complexos, etc.), strings (normais e unicodes), instâncias, etc. No jargão pythônico, usam-se os termos pickle e unpickle (e suas conjugações, quando verbo) para o ato de serializar/deserializar. Como fica meio difícil de fazer um aportuguesamento destes termos, usarei os termos serialização e deserialização mesmo. Bom, mas vamos a um exemplo simples:

CÓDIGO

import pickle

lista = [[1, 2, 'teste', 3.45, [[2, 4, 5]]]]
pickle.dump(lista, file("arquivo.pck", "w"))


O exemplo acima cria uma lista e faz com que ela seja armazenada no arquivo "arquivo.pck" (a extensão .pck não é obrigatória, pode ser qualquer uma ou mesmo nenhuma). O método dump() do módulo pickle recebe dois parâmetros: o primeiro é o objeto a ser serializado e o segundo é um objeto file ou qualquer outro que mimetize sua interface (file-like). No caso de um objeto file-like (semelhante a file), basta que ele possua um método write() que aceite uma string como parâmetro. Note que no caso de um objeto file o mesmo deve ter aberto o arquivo para gravação ("w").
Vamos agora a um exemplo de como resgatar a lista previamente serializada:

CÓDIGO

import pickle

l = pickle.load(file("arquivo.pck", "r"))
print l


O método load() retorna o objeto devidamente restaurado a partir de um objeto file ou file-like especificado. Repare que o objeto file, no exemplo acima, foi aberto para leitura ("r"). No caso de um file-like, o mesmo deve ter os métodos read() (que tem um inteiro como parâmetro) e readline() (sem parâmetros), ambos retornando uma string.

Exemplo com uma classe:

CÓDIGO

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

p1 = Pessoa('Teste', 20, '5555-5555')
print p1.nome, p1.idade, p1.telefone

import pickle

pickle.dump(p1, file('./teste.pck', 'w')) ## Armazenando

p2 = pickle.load(file('./teste.pck', 'r')) ## Recuperando
print p2.nome, p2.idade, p2.telefone


Os objetos p1 e p2 terão os mesmos valores, sendo que o primeiro é o original e o segundo foi recuperado a partir da serialização do primeiro. É importante ressaltar que somente são guardados os valores dos atributos da instância. Isso significa que a classe deve estar previamente declarada quando o objeto for restaurado. Estando no interpretador ativo, feche e abra-o novamente e experimente somente o seguinte:

CÓDIGO

p2 = pickle.load(file('./teste.pck', 'r')) ## Recuperando
print p2.nome, p2.idade, p2.telefone


O objeto não será restaurado e você receberá uma mensagem de erro. Isso porque pickle não armazena o código da classe e sim somente os dados dos atributos da instância. No caso acima, ele guarda que 'Teste', 20, '5555-5555' devem preencher, respectivamente, os atributos nome, idade e telefone de um objeto de uma classe chamada Pessoa. Daí decorre o fato de que Pessoa deve existir antes da tentativa de restauração do objeto. O seguinte exemplo realiza corretamente a restauração:

CÓDIGO

class Pessoa:
pass

p2 = pickle.load(file('./teste.pck', 'r')) ## Recuperando
print p2.nome, p2.idade, p2.telefone


Neste a classe Pessoa realmente existe (ainda que seja um pouco diferente da original), por isso a recuperação é bem sucedida.

É importante ressaltar que somente um objeto pode ser guardado, por arquivo. Não adianta abrir o arquivo para inclusão (append), pois dump() só retornará o primeiro objeto (o originalmente gravado). Todavia nada impede que juntemos em uma lista ou dicionários todos os objetos a serem gravados e que gravemos a lista ou o dicionário. Exemplo:

CÓDIGO

## Gravando:

p1 = Pessoa('Teste', 20, '5555-5555')
p2 = Pessoa('Teste2', 30, '7755-7755')
p3 = Pessoa('Teste3', 50, '5577-5588')

lista = [[p1, p2, p3]]

pickle.dump(lista, file("./teste.pck", "w"))


Levando em consideração que a classe já está declarada, podemos fazer (para recuperar):

CÓDIGO

## Recuperando:

lista = pickle.load(file("./teste.pck", "r"))

p1, p2, p3 = lista


Existe também a possibilidade de definir o que deve e o que não deve ser serializado, dentre os atributos do objeto. O mesmo se aplica à deserialização, definindo-se a maneira como um objeto será restabelecido. Isso é feito por meio de dois métodos, chamados __getstate__() e __setstate__(), que estarão na classe a ser serializada/deserializada. No caso, o método __getstate__() é chamado no ato da serialização (pickle.dump()) e __setstate__() é chamado no ato da deserialização (pickle.load()). O primeiro deve retornar um dicionário contendo os atributos a serem serializados e o segundo deve ter um parâmetro (um dicionário que será passado).
Supondo que não quisessemos guardar o atributo telefone e que o mesmo devesse sempre ser alterado para a concatenação de "7777-1111" e do nome da pessoa, nossa classe ficaria assim:

CÓDIGO

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

def __getstate__(self):
o = self.__dict__.copy()
del o[['telefone']]
return o

def __setstate__(self, d):
d[['telefone']] = "%s (%s)" % ("7777-1111", d[['nome']])
self.__dict__.update(d)


Analisemos agora: __getstate__() faz uma cópia do dicionário da instância (que é onde ficam armazenados os atributos), remove o elemento de chave "telefone" (ou seja, o atributo telefone) e retorna o dicionário modificado. Esse dicionário modificado é o que será gravado durante a serialização. Já __setstate__() faz o seguinte: ela recebe um dicionário como parâmetro (que é o mesmo dicionário que foi gravado anteriormente, retornado por __getstate__()) contendo os dados dos atributos. Dessa maneira temos como acessar o atributo nome do objeto utilizando o dicionário (o que é feito por d[['nome']]). Além disso, modificamos o mesmo dicionário, acrescentando um item de chave 'telefone' (que virá a ser o atributo telefone). Após, atualizamos o dicionário da instância com os novos valores (ou seja, os que vieram da deserialização e mais o de chave 'telefone', criado na própria rotina).

Com isso, se fizessemos...

CÓDIGO

k1 = Pessoa('Teste', 20, '5555-5555')

pickle.dump(k1, file('./teste.pck', 'w'))
k2 = pickle.load(file('./teste.pck', 'r'))

print k1.nome, k1.idade, k1.telefone
print k2.nome, k2.idade, k2.telefone


... teríamos a seguinte saída:

Teste 20 5555-5555
Teste 20 7777-1111 (Teste)

Sendo k1 o objeto originalmente criado e k2 a deserialização da serialização de k1. E mesmo que serializemos k2 (ou seja, o modificado), teríamos a mesma saída, já que as mesmas ações serão aplicadas (deletar-se-á o atributo telefone dele antes da gravação e criar-se-á o mesmo durante a restauração). Exemplo (continuando o anterior):

CÓDIGO

pickle.dump(k2, file('./teste2.pck', 'w'))
k3 = pickle.load(file('./teste2.pck', 'r'))
print k3.nome, k3.idade, k3.telefone


Cuja saída será:

Teste 20 7777-1111 (Teste)

Fora o módulo pickle, existem ainda outros módulos que realização a serialização/deserialização de dados. O módulo cPickle, por exemplo, faz o mesmo que o pickle, porém é mais rápido, devido a sua implementação em C. Vale ressaltar que existe uma compatibilidade total com arquivos criados entre os dois módulos (pickle e cPickle). O outro módulo se chamada shelve e permite o armazenamento de objetos serializados em uma estrutura de banco de dados (no caso, a serialização/deserialização é feita realmente pelo módulo pickle, e o shelve provê apenas uma maneira confortável de se trabalhar com múltiplos objetos).








Um produto Detetive.net