Désélectionner un objet python 2 avec python 3

129

Je me demande s'il existe un moyen de charger un objet qui a été picklé dans Python 2.4, avec Python 3.4.

J'ai utilisé 2to3 sur une grande quantité de code hérité de l'entreprise pour le mettre à jour.

Après avoir fait cela, lors de l'exécution du fichier, j'obtiens l'erreur suivante:

  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)

En regardant l'objet mariné en conflit, c'est un dictdans a dict, contenant des clés et des valeurs de type str.

Ma question est donc la suivante: existe-t-il un moyen de charger un objet, initialement picklé en python 2.4, avec python 3.4?

NDevox
la source
1
Python 2.4 a-t-il le jsonmodule? Vous pourriez peut-être écrire un script 2.4 qui décoche l'objet et l'enregistre en tant qu'objet json, puis écrire un script 3.4 qui lit l'objet json et l'enregistre en tant qu'objet pickle compatible 3.4. Ce serait une opération unique que vous exécutez sur tous vos fichiers pickle.
Kevin le
Je pensais dans le même sens, étant donné que ce sont des dictionnaires, je pense que je pourrais simplement changer sys.stdout en fichier et les imprimer, mais je veux voir si je peux les charger en premier
NDevox
Question connexe concernant spécifiquement les datetimes: stackoverflow.com/questions/24805105/…
John Y

Réponses:

189

Vous devrez dire pickle.load()comment convertir les données Python bytestring en chaînes Python 3, ou vous pouvez dire picklede les laisser sous forme d'octets.

La valeur par défaut est d'essayer de décoder toutes les données de chaîne au format ASCII, et ce décodage échoue. Consultez la pickle.load()documentation :

Les arguments de mot-clé facultatifs sont fix_imports , encoding et errors , qui sont utilisés pour contrôler la compatibilité du flux pickle généré par Python 2. Si fix_imports est true, pickle essaiera de mapper les anciens noms Python 2 aux nouveaux noms utilisés dans Python 3. Le l'encodage et les erreurs indiquent à Pickle comment décoder des instances de chaîne 8 bits picklées par Python 2; ceux-ci par défaut sont respectivement «ASCII» et «strict». Le codage peut être des «octets» pour lire ces instances de chaîne de 8 bits en tant qu'objets octets.

Le paramétrage du codage sur latin1vous permet d'importer directement les données:

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

mais vous devrez vérifier qu'aucune de vos chaînes n'est décodée en utilisant le mauvais codec; Latin-1 fonctionne pour n'importe quelle entrée car il mappe directement les valeurs d'octet 0-255 aux 256 premiers points de code Unicode.

L'alternative serait de charger les données avec encoding='bytes'et de décoder toutes les bytesclés et valeurs par la suite.

Notez que jusqu'aux versions Python antérieures à 3.6.8, 3.7.2 et 3.8.0, le décolmatage des datetimedonnées d'objet Python 2 est interrompu sauf si vous utilisez encoding='bytes'.

Martijn Pieters
la source
1
Comment cela pourrait-il être rétrocompatible avec Python 2? Apparemment, l'argument d'encodage n'est pas présent pour Python 2.
EpicAdv
2
@EpicAdv: vous n'avez pas besoin de rendre ce code compatible avec Python 2; cette question porte sur la façon de charger les pickles Python 2 dans Python 3. Supprimez encodingcomplètement le mot clé pour Python 2.
Martijn Pieters
10
@EpicAdv: Vous pouvez créer un dictionnaire pickle_options qui est soit vide pour python 2, soit a 'encoding': 'latin1'et envoie ** pickle_options à pickle. De cette façon, il devrait fonctionner dans les deux versions.
syngnathe
@pipefish - Intelligent, mais quelque part, vous devez détecter la version que vous utilisez, vous pouvez donc aussi plus simplement faire l'appel différemment (un avec et un sans l'argument supplémentaire) en fonction de la version. Mais au moins, vous avez l'essentiel du commentaire d'EpicAdv, que le commentaire de Martijn ne répond pas du tout.
John Y
2
Je me rends compte que le datetimecommentaire n'était pas l'objectif principal de cette réponse, mais pour les futurs lecteurs, j'aimerais souligner que même les versions "fixes" de Python 3 nécessitent encore encoding='latin-1'de décocher les datetimes Python 2. Si vos données Python 2 marinées incluent à la fois des datetimes et des chaînes d'octets encodés dans autre chose que Latin-1, alors vous feriez peut-être encore mieux d'utiliser encoding='bytes'après tout.
John Y
15

L'utilisation encoding='latin1'provoque certains problèmes lorsque votre objet contient des tableaux numpy.

L'utilisation encoding='bytes'sera meilleure.

Veuillez consulter cette réponse pour une explication complète de l'utilisationencoding='bytes'

Sreeragh AR
la source
Quels problèmes? À quoi dois-je faire attention? using bytestransforme les chaînes en bytes (), donc je préfère latin1si possible, mais je ne sais pas quel est le problème.
Gulzar le
2
@ sreeragh-ar: Pouvez-vous donner un exemple des problèmes que vous avez rencontrés? J'ai un bidimensionnel numpy.ndarray(numpy 1.14) mariné dans Python 2.7 en utilisant cPickle.dumps(), et unpickling dans Python 3 avec pickle.loads(..., encoding='latin1')fonctionne bien.
djvg le
@djvg J'ai rencontré des problèmes lorsque j'ai dû choisir des images comme chaîne d'image et les décoller. Le code peut être trouvé ici. gist.github.com/sreeragh-ar/70205db3a43badbfa69f758faa898be3
Sreeragh AR
@Gulzar Veuillez voir l'essentiel ci-dessus pour le problème. Les images étaient corrompues après le décapage.
Sreeragh AR le