Salvando um objeto (persistência de dados)


233

Eu criei um objeto como este:

company1.name = 'banana' 
company1.value = 40

Eu gostaria de salvar este objeto. Como eu posso fazer isso?


1
Veja o exemplo de pessoas que vêm aqui para um exemplo simples de como usar pickle.
Martin Thoma

@MartinThoma: Por que você (aparentemente) prefere essa resposta à aceita (da pergunta vinculada )?
martineau

No momento em que vinculei, a resposta aceita não tinha protocol=pickle.HIGHEST_PROTOCOL. Minha resposta também oferece alternativas para picles.
Martin Thoma

Respostas:


449

Você pode usar o picklemódulo na biblioteca padrão. Aqui está uma aplicação elementar para o seu exemplo:

import pickle

class Company(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value

with open('company_data.pkl', 'wb') as output:
    company1 = Company('banana', 40)
    pickle.dump(company1, output, pickle.HIGHEST_PROTOCOL)

    company2 = Company('spam', 42)
    pickle.dump(company2, output, pickle.HIGHEST_PROTOCOL)

del company1
del company2

with open('company_data.pkl', 'rb') as input:
    company1 = pickle.load(input)
    print(company1.name)  # -> banana
    print(company1.value)  # -> 40

    company2 = pickle.load(input)
    print(company2.name) # -> spam
    print(company2.value)  # -> 42

Você também pode definir seu próprio utilitário simples, como o seguinte, que abre um arquivo e grava um único objeto nele:

def save_object(obj, filename):
    with open(filename, 'wb') as output:  # Overwrites any existing file.
        pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)

# sample usage
save_object(company1, 'company1.pkl')

Atualizar

Como essa é uma resposta tão popular, gostaria de abordar alguns tópicos de uso um pouco avançados.

cPickle(ou _pickle) vspickle

É quase sempre preferível usar o cPicklemódulo, em vez de pickleporque o primeiro é escrito em C e é muito mais rápido. Existem algumas diferenças sutis entre eles, mas na maioria das situações eles são equivalentes e a versão C fornecerá um desempenho muito superior. Mudar para ele não poderia ser mais fácil, basta alterar a importdeclaração para isso:

import cPickle as pickle

No Python 3, cPicklefoi renomeado _pickle, mas isso não é mais necessário, já que o picklemódulo agora o faz automaticamente - consulte Que diferença entre pickle e _pickle no python 3? .

O resumo é que você pode usar algo como o seguinte para garantir que seu código sempre use a versão C quando estiver disponível no Python 2 e 3:

try:
    import cPickle as pickle
except ModuleNotFoundError:
    import pickle

Formatos de fluxo de dados (protocolos)

picklepode ler e gravar arquivos em vários formatos diferentes, específicos para Python, chamados protocolos, conforme descrito na documentação , "Protocolo versão 0" é ASCII e, portanto, "legível por humanos". As versões> 0 são binárias e a mais alta disponível depende de qual versão do Python está sendo usada. O padrão também depende da versão do Python. No Python 2, o padrão era a versão Protocol 0, mas no Python 3.8.1, é a versão Protocol 4. No Python 3.x, o módulo foi pickle.DEFAULT_PROTOCOLadicionado, mas isso não existe no Python 2.

Felizmente, existe uma abreviação para escrever pickle.HIGHEST_PROTOCOLem todas as chamadas (assumindo que é isso que você deseja e costuma fazer), basta usar o número literal -1- semelhante a referenciar o último elemento de uma sequência por meio de um índice negativo. Então, em vez de escrever:

pickle.dump(obj, output, pickle.HIGHEST_PROTOCOL)

Você pode simplesmente escrever:

pickle.dump(obj, output, -1)

De qualquer forma, você só especificaria o protocolo uma vez se tivesse criado um Picklerobjeto para uso em várias operações de pickle:

pickler = pickle.Pickler(output, -1)
pickler.dump(obj1)
pickler.dump(obj2)
   etc...

Nota : Se você estiver em um ambiente executando versões diferentes do Python, provavelmente desejará usar explicitamente (por exemplo, código rígido) um número de protocolo específico que todos possam ler (versões posteriores geralmente podem ler arquivos produzidos por versões anteriores) .

Vários Objetos

Enquanto um arquivo de picles pode conter qualquer número de objetos em conserva, como mostrado nas amostras acima, quando há um número desconhecido deles, muitas vezes é mais fácil de armazenar todos eles em algum tipo de recipiente de tamanho variável, como um list, tupleou dicte escrever todos eles para o arquivo em uma única chamada:

tech_companies = [
    Company('Apple', 114.18), Company('Google', 908.60), Company('Microsoft', 69.18)
]
save_object(tech_companies, 'tech_companies.pkl')

e restaure a lista e tudo mais tarde com:

with open('tech_companies.pkl', 'rb') as input:
    tech_companies = pickle.load(input)

A principal vantagem é que você não precisa saber quantas instâncias de objetos são salvas para carregá-las novamente mais tarde (embora fazer isso sem essas informações seja possível, isso requer algum código um pouco especializado). Veja as respostas para a pergunta relacionada Salvando e carregando vários objetos no arquivo pickle? para obter detalhes sobre diferentes maneiras de fazer isso. Pessoalmente, eu gosto mais da resposta de @Lutz Prechelt . Aqui está adaptado aos exemplos aqui:

class Company:
    def __init__(self, name, value):
        self.name = name
        self.value = value

def pickled_items(filename):
    """ Unpickle a file of pickled data. """
    with open(filename, "rb") as f:
        while True:
            try:
                yield pickle.load(f)
            except EOFError:
                break

print('Companies in pickle file:')
for company in pickled_items('company_data.pkl'):
    print('  name: {}, value: {}'.format(company.name, company.value))

1
Isso é raro para mim, porque imaginei que haveria uma maneira mais fácil de salvar um objeto ... Algo como 'saveobject (company1, c: \ mypythonobjects) #
Peterstone

4
@ Peterstone: Se você apenas quisesse armazenar um objeto, precisaria apenas da metade do código que no meu exemplo - escrevi propositadamente da mesma maneira que fiz para mostrar como mais de um objeto pode ser salvo (e depois lido novamente de) o mesmo arquivo.
martineau

1
@ Peterstone, há uma boa razão para a separação de responsabilidades. Dessa forma, não há limitações quanto ao uso dos dados do processo de decapagem. Você pode armazená-lo em disco ou também pode enviá-lo através de uma conexão de rede.
Harald Scheirich

3
@martinaeau, isso foi em resposta a comentários perstones sobre um deve ter apenas uma função para salvar um objeto no disco. A responsabilidade do pickles é apenas transformar um objeto em dados que podem ser manipulados como um pedaço. Escrever coisas no arquivo é responsabilidade dos objetos do arquivo. Ao manter as coisas separadas uma possibilita maior reutilização por exemplo, ser capaz de enviar os dados em conserva accross uma conexão de rede ou armazená-lo em um banco de dados, todas as responsabilidades separar os dados reais <-> conversão objeto
Harald Scheirich

1
Você exclui company1e company2. Por que você também não exclui Companye mostra o que acontece?
Mike McKerns

49

Eu acho que é uma suposição bastante forte supor que o objeto é a class. E se não for um class? Há também a suposição de que o objeto não foi definido no intérprete. E se foi definido no intérprete? Além disso, e se os atributos fossem adicionados dinamicamente? Quando alguns objetos python têm atributos adicionados aos seus __dict__após a criação, picklenão respeita a adição desses atributos (ou seja, 'esquece' eles foram adicionados - porque pickleserializa por referência à definição do objeto).

Em todos esses casos, picklee cPicklepode falhar terrivelmente.

Se você deseja salvar um object(criado arbitrariamente), onde possui atributos (adicionados na definição do objeto ou posteriormente) ... sua melhor aposta é usar dill, que pode serializar quase tudo em python.

Começamos com uma aula…

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> with open('company.pkl', 'wb') as f:
...     pickle.dump(company1, f, pickle.HIGHEST_PROTOCOL)
... 
>>> 

Agora desligue e reinicie ...

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> with open('company.pkl', 'rb') as f:
...     company1 = pickle.load(f)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1378, in load
    return Unpickler(file).load()
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 858, in load
dispatch[key](self)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1090, in load_global
    klass = self.find_class(module, name)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1126, in find_class
    klass = getattr(mod, name)
AttributeError: 'module' object has no attribute 'Company'
>>> 

Opa ... picklenão aguento. Vamos tentar dill. Vamos lançar outro tipo de objeto (a lambda) para uma boa medida.

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill       
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> 
>>> company2 = lambda x:x
>>> company2.name = 'rhubarb'
>>> company2.value = 42
>>> 
>>> with open('company_dill.pkl', 'wb') as f:
...     dill.dump(company1, f)
...     dill.dump(company2, f)
... 
>>> 

E agora leia o arquivo.

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> with open('company_dill.pkl', 'rb') as f:
...     company1 = dill.load(f)
...     company2 = dill.load(f)
... 
>>> company1 
<__main__.Company instance at 0x107909128>
>>> company1.name
'banana'
>>> company1.value
40
>>> company2.name
'rhubarb'
>>> company2.value
42
>>>    

Funciona. A razão picklefalha, e dillnão, é que dilltrata __main__como um módulo (na maior parte), e também pode selecionar definições de classe em vez de selecionar por referência (como picklefaz). O motivo pelo qual dillum pickle lambdaé dado é que ele dá um nome ... então a magia do pickling pode acontecer.

Na verdade, há uma maneira mais fácil de salvar todos esses objetos, especialmente se você tiver muitos objetos criados. Apenas despeje a sessão inteira do python e volte mais tarde.

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> class Company:
...     pass
... 
>>> company1 = Company()
>>> company1.name = 'banana'
>>> company1.value = 40
>>> 
>>> company2 = lambda x:x
>>> company2.name = 'rhubarb'
>>> company2.value = 42
>>> 
>>> dill.dump_session('dill.pkl')
>>> 

Agora desligue o computador, desfrute de um café expresso ou o que quer que seja e volte mais tarde ...

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> dill.load_session('dill.pkl')
>>> company1.name
'banana'
>>> company1.value
40
>>> company2.name
'rhubarb'
>>> company2.value
42
>>> company2
<function <lambda> at 0x1065f2938>

A única grande desvantagem é que dillnão faz parte da biblioteca padrão do python. Portanto, se você não pode instalar um pacote python no seu servidor, não pode usá-lo.

No entanto, se você conseguir instalar pacotes python no seu sistema, poderá obter as informações mais recentes dillcom git+https://github.com/uqfoundation/dill.git@master#egg=dill. E você pode obter a versão mais recente lançada com pip install dill.


Estou recebendo um TypeError: __new__() takes at least 2 arguments (1 given)ao tentar usar dill(o que parece promissor) com um objeto bastante complexo que inclui um arquivo de áudio.
precisa saber é o seguinte

1
@ MikeiLL: Você está recebendo um TypeErrorquando faz o que, exatamente? Isso geralmente é um sinal de ter o número errado de argumentos ao instanciar uma instância de classe. Se isso não fizer parte do fluxo de trabalho da pergunta acima, você poderá publicá-la como outra pergunta, enviá-la por e-mail ou adicioná-la como um problema na dillpágina do github?
Mike McKerns

3
Para quem acompanha, aqui está a pergunta relacionada @MikeLL postada - a partir da resposta, aparentemente não foi um dillproblema.
martineau

dilEu me dou MemoryErrorembora! o mesmo acontece cPickle, picklee hickle.
Färid Alijani 10/10

4

Você pode usar o anycache para fazer o trabalho por você. Ele considera todos os detalhes:

  • Ele usa dill como back-end, o que estende o picklemódulo python para lidar com lambdatodos os recursos legais do python.
  • Ele armazena objetos diferentes em arquivos diferentes e os recarrega adequadamente.
  • Limita o tamanho do cache
  • Permite limpeza de cache
  • Permite o compartilhamento de objetos entre várias execuções
  • Permite o respeito aos arquivos de entrada que influenciam o resultado

Supondo que você tenha uma função myfuncque cria a instância:

from anycache import anycache

class Company(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value

@anycache(cachedir='/path/to/your/cache')    
def myfunc(name, value)
    return Company(name, value)

O Anycache chama myfuncna primeira vez e seleciona o resultado em um arquivo cachedirusando um identificador exclusivo (dependendo do nome da função e de seus argumentos) como nome do arquivo. Em qualquer execução consecutiva, o objeto em conserva é carregado. Se o cachedirfor preservado entre execuções python, o objeto pickled será obtido da execução python anterior.

Para mais detalhes, consulte a documentação


Como alguém usaria anycachepara salvar mais de uma instância de, digamos, um classou contêiner como um list(que não foi o resultado da chamada de uma função)?
martineau

2

Exemplo rápido usando company1da sua pergunta, com python3.

import pickle

# Save the file
pickle.dump(company1, file = open("company1.pickle", "wb"))

# Reload the file
company1_reloaded = pickle.load(open("company1.pickle", "rb"))

No entanto, como essa resposta observou, o pickle geralmente falha. Então você realmente deve usar dill.

import dill

# Save the file
dill.dump(company1, file = open("company1.pickle", "wb"))

# Reload the file
company1_reloaded = dill.load(open("company1.pickle", "rb"))
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.