web-dev-qa-db-fra.com

Comment décaper et décoder une chaîne portable en Python 3

Je dois choisir un objet Python3 dans une chaîne que je souhaite dissocier d'une variable d'environnement dans une version Travis CI. Le problème est que je n'arrive pas à trouver un moyen de choisir une chaîne portable (unicode) en Python3:

import os, pickle    

from my_module import MyPickleableClass


obj = {'cls': MyPickleableClass, 'other_stuf': '(...)'}

pickled = pickle.dumps(obj)

# raises TypeError: str expected, not bytes
os.environ['pickled'] = pickled

# raises UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbb (...)
os.environ['pickled'] = pickled.decode('utf-8')

pickle.loads(os.environ['pickled'])

Existe-t-il un moyen de sérialiser des objets complexes tels que datetime.datetime en unicode ou en une autre représentation de chaîne en Python3 que je puisse transférer vers une autre machine et désérialiser?

Mettre à jour

J'ai testé les solutions suggérées par @kindall, mais la pickle.dumps(obj, 0).decode() soulève une UnicodeDecodeError. Néanmoins, l’approche base64 fonctionne, mais il faut un extra décodage/encodage étape. La solution fonctionne à la fois sur Python2.x et Python3.x.

# encode returns bytes so it needs to be decoded to string
pickled = pickle.loads(codecs.decode(pickled.encode(), 'base64')).decode()

type(pickled)  # <class 'str'>

unpickled = pickle.loads(codecs.decode(pickled.encode(), 'base64'))
10
Peter Hudec

pickle.dumps() produit un objet bytes. S'attendre à ce que ces octets arbitraires soient du texte UTF-8 valide (l'hypothèse que vous formulez en essayant de le décoder en une chaîne à partir de UTF-8) est plutôt optimiste. Ce serait une coïncidence si cela fonctionnait!

Une solution consiste à utiliser l'ancien protocole de décapage, qui utilise entièrement les caractères ASCII. Ceci apparaît toujours sous la forme bytes, mais puisqu'il s'agit uniquement d'ASCII, il peut être décodé en chaîne sans contrainte:

pickled = pickled.dumps(obj, 0).decode()

Vous pouvez également utiliser une autre méthode de codage pour coder un objet choisi en mode binaire, tel que base64:

import codecs
pickled = codecs.encode(pickle.dumps(obj), "base64").decode()

Le décodage serait alors:

unpickled = pickle.loads(codecs.decode(pickled.encode(), "base64"))

Utiliser pickle avec le protocole 0 semble donner des chaînes plus courtes que les pickles binaires codant en base64 (et la suggestion d'abarnert de codage hexadécimal va être encore plus grande que celle en base64), mais je ne l'ai pas testé rigoureusement ni quoi que ce soit. Testez-le avec vos données et voyez.

18
kindall

Si vous souhaitez stocker des octets dans l'environnement, au lieu du texte codé, c'est à cela que sert - environb .

Cela ne fonctionne pas sous Windows. (Comme le suggère la documentation, vous devriez vérifier os.supports_bytes_environ si vous êtes sur 3.2+ au lieu de supposer que Unix le fait et que Windows ne le fait pas…) Donc, pour cela, vous devrez passer les octets en quelque chose qui peut être codé quel que soit le codage de votre système, par exemple, en utilisant backslash-escape ou même hex. Donc, par exemple:

if os.supports_bytes_environ:
    environb['pickled'] = pickled
else:
    environ['pickled'] = codecs.encode(pickled, 'hex')
1
abarnert

Je pense que la réponse la plus simple, surtout si vous ne vous souciez pas de Windows, consiste simplement à stocker les octets dans l'environnement, comme suggéré dans mon autre réponse .

Mais si vous voulez quelque chose de propre et de débogable, vous serez peut-être plus heureux d’utiliser quelque chose conçu comme un format texte.

pickle a un protocole "texte brut" 0, comme expliqué dans kindall's answer . C'est certainement plus lisible que le protocole 3 ou 4, mais ce n'est toujours pas quelque chose que je voudrais en fait lire vouloir.

JSON est beaucoup plus agréable, mais il ne supporte pas datetime en sortie de boîte. Vous pouvez créer votre propre encodage (le module json module est extensible dans la bibliothèque stdlib) pour la poignée de types que vous devez encoder, ou utiliser un paramètre tel que jsonpickle . Il est généralement plus sûr, plus efficace et plus lisible de créer des encodages personnalisés pour chaque type de fichier qui vous tient à coeur, plutôt qu'un schéma général "empaquetez les types arbitraires dans un protocole complet" tel que pickle ou jsonpickle, mais bien sûr, cela demande aussi plus de travail, surtout si vous avez beaucoup de types supplémentaires.

Schéma JSON vous permet de définir des langues en JSON, similaires à celles que vous feriez en XML. Il est livré avec un date-timeformat de chaîne intégré , et la bibliothèque jsonschema pour Python sait comment l'utiliser. 

YAML possède un référentiel d’extensions standard qui comprend de nombreux types, dont JSON, notamment un timestamp . La plupart des les zillions 'yaml' modules pour Python savent déjà comment encoder des objets datetime vers et à partir de ce type. Si vous avez besoin de types supplémentaires autres que ceux inclus dans YAML, il a été conçu pour être extensible de manière déclarative. Et il y a des bibliothèques qui font l'équivalent de jsonpickle, définissant de nouveaux types à la volée, si vous en avez vraiment besoin.

Et enfin, vous pouvez toujours écrire un langage XML.

0
abarnert