web-dev-qa-db-ja.com

Python 3でポータブル文字列にピクルスを付けたり外したりする方法

Travis CIビルドの環境変数から取り出したいPython3オブジェクトを文字列にピクルする必要があります。問題は、Python3で移植可能な文字列(Unicode)にピクルする方法が見つからないことです。

_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'])
_

_datetime.datetime_のような複雑なオブジェクトをユニコードに、または別のマシンに転送して逆シリアル化できるPython3の他の文字列表現にシリアル化する方法はありますか?

更新

@kindallによって提案されたソリューションをテストしましたが、pickle.dumps(obj, 0).decode()UnicodeDecodeErrorを発生させます。それでもbase64アプローチは機能しますが、extra decode/encodeステップが必要でした。このソリューションは、Python2.xと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'))
_
21
Peter Hudec

pickle.dumps()は、bytesオブジェクトを生成します。これらの任意のバイトが有効なUTF-8テキストであると期待すること(UTF-8から文字列にデコードしようとすることで想定していること)はかなり楽観的です。うまくいったら偶然でしょう!

1つの解決策は、完全にASCII文字を使用する古い酸洗いプロトコルを使用することです。これはまだbytesとして出力されますが、ASCIIのみなので、文字列にデコードできます。ストレスなし:

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

また、他のエンコード方法を使用して、base64などのバイナリピクル処理されたオブジェクトをテキストにエンコードすることもできます。

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

デコードは次のようになります:

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

プロトコル0でpickleを使用すると、base64エンコードのバイナリピクルよりも文字列が短くなるように思われます(そして、16進数エンコードのabarnertの提案は、base64よりもさらに大きくなるでしょう)が、厳密にまたは何もテストしていません。あなたのデータでそれをテストし、見てください。

29
kindall

エンコードされたテキストの代わりにバイトを環境に格納する場合は、それが environb の目的です。

これはWindowsでは機能しません。 (ドキュメントが示唆するように、あなたはチェックする必要があります os.supports_bytes_environ UnixがそうでWindowsがそうではないと仮定するのではなく、3.2 +を使用している場合…そのためには、システムエンコーディングに関係なく、エンコードできるものにバイトを密輸する必要があります。たとえば、backslash-escape、またはhex。したがって、たとえば:

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

最も簡単な答えは、特にWindowsに関心がない場合は、 で提案されているように、環境にバイトを格納するだけであると思います私の他の答え

ただし、クリーンでデバッグ可能なものが必要な場合は、テキストベースのフォーマットとして設計されたものを使用した方がよいでしょう。

pickleには、 kindallの回答 で説明されているように、「プレーンテキスト」プロトコル0があります。それは確かにプロトコル3または4より読みやすいですが、それでも私が実際に読みたいものではありません

[〜#〜] json [〜#〜] はより優れていますが、そのままではdatetimeを処理できません。エンコードする必要のある少数の型について、独自のエンコード(stdlibの json モジュールは拡張可能)を考え出すか、 jsonpickle のようなものを使用できます。 picklejsonpickleのような一般的な「チューリングコンプリートプロトコルに任意の型をパックする」方式よりも、気になる各型のカスタムエンコーディングを考え出す方が、一般に安全で効率的で読みやすいですが、もちろんより多くの作業も必要です。特にあなたが多くの余分なタイプを持っているなら。

JSONスキーマ では、XMLで行うのと同様に、JSONで言語を定義できます。組み込みのdate-time文字列形式 が付属しており、 jsonschema ライブラリPythonはその使用方法を知っています。

[〜#〜] yaml [〜#〜] には、 タイムスタンプ など、JSONにはない多くのタイプを含む標準の拡張リポジトリがあります。ほとんどの Python のジリオン 'yaml'モジュールは、この型との間でdatetimeオブジェクトをエンコードする方法をすでに知っています。YAMLに含まれるもの以外に追加の型が必要な場合は、宣言的に拡張できるように設計されており、jsonpickleと同等の処理を行うライブラリがあり、本当に必要な場合にその場で新しい型を定義します。

そして最後に、いつでもXML言語を作成できます。

1
abarnert