web-dev-qa-db-ja.com

Pythonモジュールのディレクトリを変更した後のpickle化

最近、プログラムのディレクトリレイアウトを変更しました。以前は、すべてのモジュールを「メイン」フォルダ内に配置していました。今、私はそれらをプログラムにちなんだ名前のディレクトリに移動し、__init__.pyパッケージを作成します。

これで、メインディレクトリに単一の.pyファイルがあり、プログラムの起動に使用できます。

とにかく、私のプログラムの以前のバージョンからピクルスにしたファイルをロードしようとすると失敗します。 「ImportError:ツールという名前のモジュールはありません」というメッセージが表示されます。これは、以前のモジュールがメインフォルダーにあり、現在は単純なツールではなく、whyteboard.toolsにあるためです。ただし、toolsモジュールにインポートするコードは、同じディレクトリにあるため、パッケージを指定する必要はないでしょう。

したがって、私のプログラムディレクトリは次のようになります。

whyteboard-0.39.4

-->whyteboard.py

-->README.txt

-->CHANGELOG.txt

---->whyteboard/

---->whyteboard/__init__.py

---->whyteboard/gui.py

---->whyteboard/tools.py

whyteboard.pyは、whyteboard/gui.pyからコードのブロックを起動し、GUIを起動します。このピクリングの問題は、ディレクトリが再編成される前には発生していませんでした。

50
Steven Sproat

pickle's docs が言うように、クラスインスタンス(実際には関数も)を保存および復元するには、特定の制約を尊重する必要があります。

pickleはクラスインスタンスを透過的に保存および復元できますが、クラス定義はインポート可能であり、オブジェクトが格納されたときと同じモジュールに存在する必要があります

whyteboard.toolsではない「同じモジュール」tools(同じパッケージ内の他のモジュールがimport toolsによってインポートできる場合でも) 、それは最終的にsys.modulesとしてsys.modules['whyteboard.tools']として終了します。これは絶対に重要です。そうでない場合、同じパッケージ内の1つによってインポートされた同じモジュールと別のパッケージ内の1つによってインポートされた同じモジュールは、複数の、場合によっては競合するエントリで終了します!)。

Pickleファイルが(互換性の理由でのみデフォルトである古いascii形式とは対照的に)優れた/高度な形式である場合、そのような変更を行った後に移行すると、実際にはできない場合があります別の答えが示唆していることにかかわらず、「ファイルの編集」(バイナリ&c ...!)と同じくらい簡単です。代わりに、小さな "pickle-migratingスクリプト"を作成することをお勧めします。次のようにsys.modulesにパッチを当てます...:

import sys
from whyteboard import tools

sys.modules['tools'] = tools

次に、cPickle.load各ファイル、del sys.modules['tools']、およびcPickle.dumpの各ロード済みオブジェクトをファイルに戻します。sys.modulesの一時的な追加エントリにより、ピクルが正常にロードされ、再度ダンプすると、インスタンスのクラスに適切なモジュール名が使用されます(削除されます)追加のエントリはそれを確認する必要があります)。

65
Alex Martelli

私に起こった、pickleをロードする前にsys.pathにモジュールの新しい場所を追加することで解決しました:

import sys
sys.path.append('path/to/whiteboard')
f = open("pickled_file", "rb")
pickle.load(f)
11
Ranch

これは、 find_class() を使用するカスタム「unpickler」で実行できます。

_import io
import pickle


class RenameUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        renamed_module = module
        if module == "tools":
            renamed_module = "whyteboard.tools"

        return super(RenameUnpickler, self).find_class(renamed_module, name)


def renamed_load(file_obj):
    return RenameUnpickler(file_obj).load()


def renamed_loads(pickled_bytes):
    file_obj = io.BytesIO(pickled_bytes)
    return renamed_load(file_obj)
_

次に、renamed_load()の代わりにpickle.load()を使用し、renamed_loads()の代わりにpickle.loads()を使用する必要があります。

7
bossylobster

pickleは、参照によってクラスをシリアル化するため、クラスが変更された場合、クラスが見つからないため、アンピックされません。 dillの代わりにpickleを使用する場合、参照によって、または直接(クラスをインポートパスの代わりに直接シリアル化することによって)クラスをシリアル化できます。 dumpの後でloadの前にクラス定義を変更するだけで、これをかなり簡単にシミュレートできます。

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 Foo(object):
...   def bar(self):
...     return 5
... 
>>> f = Foo()
>>> 
>>> _f = dill.dumps(f)
>>> 
>>> class Foo(object):
...   def bar(self, x):
...     return x
... 
>>> g = Foo()
>>> f_ = dill.loads(_f)
>>> f_.bar()
5
>>> g.bar(4)
4
6
Mike McKerns

これはピクルスの通常の動作です。ピクルされていないオブジェクトには、 定義モジュールのインポート可能 が必要です。

通常は単純なテキストファイルであるため、ピクル処理されたファイルを編集することにより、モジュールパスを変更できます(つまり、toolsからwhyteboard.toolsに)。

5
Luper Rouch