Desfazer a seleção de um objeto python 2 com python 3

129

Eu estou querendo saber se existe uma maneira de carregar um objeto que foi em conserva em Python 2.4, com Python 3.4.

Eu tenho executado o 2to3 em uma grande quantidade de código legado da empresa para atualizá-lo.

Feito isso, ao executar o arquivo, recebo o seguinte erro:

  File "H:\fixers - 3.4\addressfixer - 3.4\trunk\lib\address\address_generic.py"
, line 382, in read_ref_files
    d = pickle.load(open(mshelffile, 'rb'))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 1: ordinal
not in range(128)

Observando o objeto em conserva na disputa, é um dictem um dict, contendo chaves e valores do tipo str.

Então, minha pergunta é: Existe uma maneira de carregar um objeto, originalmente em conserva no python 2.4, com o python 3.4?

NDevox
fonte
1
O Python 2.4 possui o jsonmódulo? Talvez você possa escrever um script 2.4 que remova o objeto e o salve como objeto json e, em seguida, escreva um script 3.4 que leia o objeto json e salve-o como um objeto pickle compatível com 3.4. Essa seria uma operação única executada em todos os seus arquivos pickle.
Kevin
Eu estava pensando em moldes semelhantes, considerando que estes são dicts Eu acho que eu poderia apenas mudar sys.stdout para um arquivo e imprimi-los, mas eu quero ver se eu posso carregá-los primeiro
NDevox
Pergunta relacionada que diz respeito especificamente às datetime: stackoverflow.com/questions/24805105/…
John Y

Respostas:

189

Você precisará dizer pickle.load()como converter dados de Python bytestring em strings do Python 3 ou pode pickledeixá-los em bytes.

O padrão é tentar decodificar todos os dados da cadeia como ASCII, e essa decodificação falhará. Veja a pickle.load()documentação :

Os argumentos opcionais da palavra-chave são fix_imports , codificação e erros , usados ​​para controlar o suporte de compatibilidade para o fluxo de pickle gerado pelo Python 2. Se fix_imports for verdadeiro, pickle tentará mapear os nomes antigos do Python 2 para os novos nomes usados ​​no Python 3. codificação e erros informam ao pickle como decodificar instâncias de string de 8 bits selecionadas pelo Python 2; esses padrões são 'ASCII' e 'strict', respectivamente. A codificação pode ser 'bytes' para ler essas instâncias de cadeia de 8 bits como objetos de bytes.

Definir a codificação como latin1permite importar os dados diretamente:

with open(mshelffile, 'rb') as f:
    d = pickle.load(f, encoding='latin1') 

mas você precisará verificar se nenhuma de suas seqüências de caracteres foi decodificada usando o codec errado; O Latin-1 funciona para qualquer entrada, pois mapeia os valores de bytes de 0 a 255 para os primeiros 256 pontos de código Unicode diretamente.

A alternativa seria carregar os dados encoding='bytes'e decodificar todas as byteschaves e valores posteriormente.

Observe que, até as versões Python anteriores a 3.6.8, 3.7.2 e 3.8.0, a remoção dos datetimedados do objeto Python 2 é interrompida, a menos que você o use encoding='bytes'.

Martijn Pieters
fonte
1
Como isso pode ser tornado compatível com o Python 2? Aparentemente, o argumento de codificação não está presente no Python 2.
EpicAdv
2
@ EpicAdv: você não precisa tornar esse código compatível com o Python 2; Esta pergunta é sobre como carregar Python 2 pickles em Python 3. Drop the encodingpalavra-chave por completo para Python 2.
Martijn Pieters
10
@EpicAdv: você pode criar um dicionário pickle_options vazio para python 2 ou que possui 'encoding': 'latin1'e enviar ** pickle_options para pickle. Dessa forma, ele deve ser executado nas duas versões.
pipefish
@pipefish - Inteligente, mas em algum lugar é necessário detectar qual versão você está usando, para que você também possa fazer mais claramente a chamada de maneira diferente (uma com e outra sem o argumento extra), dependendo da versão. Mas pelo menos você entendeu o comentário do EpicAdv, que o comentário de Martijn não aborda.
John Y
2
Sei que o datetimecomentário não foi o principal objetivo desta resposta, mas, para futuros leitores, gostaria de ressaltar que mesmo as versões "fixas" do Python 3 ainda precisam encoding='latin-1'desatar as datas do Python 2. Se seus dados selecionados do Python 2 incluirem datetime e bytestrings codificados em algo que não seja o Latin-1, você ainda poderá usar melhor, encoding='bytes'afinal.
John Y
15

O uso encoding='latin1'causa alguns problemas quando seu objeto contém matrizes numpy.

Usar encoding='bytes'será melhor.

Consulte esta resposta para obter uma explicação completa do usoencoding='bytes'

Sreeragh AR
fonte
Quais questões? Com o que devo ter cuidado? usando bytesfaz strings em bytes (), então prefiro, latin1se possível, mas não está claro para mim qual é o problema.
Gulzar
2
@ sreeragh-ar: você poderia dar um exemplo dos problemas que encontrou? Eu tenho um bidimensional numpy.ndarray(numpy 1.14) em conserva em Python 2.7 usando cPickle.dumps(), e unpickling em Python 3 com pickle.loads(..., encoding='latin1')funciona bem.
djvg 17/01
@djvg Eu enfrentei problemas quando tive que escolher imagens como uma sequência de imagens e removê-las. O código pode ser encontrado aqui. gist.github.com/sreeragh-ar/70205db3a43badbfa69f758faa898be3
Sreeragh AR
@Gulzar Por favor, veja a lista acima para o problema. As imagens foram corrompidas após o cancelamento.
Sreeragh AR 17/01