最近、プログラムのディレクトリレイアウトを変更しました。以前は、すべてのモジュールを「メイン」フォルダ内に配置していました。今、私はそれらをプログラムにちなんだ名前のディレクトリに移動し、__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を起動します。このピクリングの問題は、ディレクトリが再編成される前には発生していませんでした。
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
の一時的な追加エントリにより、ピクルが正常にロードされ、再度ダンプすると、インスタンスのクラスに適切なモジュール名が使用されます(削除されます)追加のエントリはそれを確認する必要があります)。
私に起こった、pickleをロードする前にsys.pathにモジュールの新しい場所を追加することで解決しました:
import sys
sys.path.append('path/to/whiteboard')
f = open("pickled_file", "rb")
pickle.load(f)
これは、 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()
を使用する必要があります。
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
これはピクルスの通常の動作です。ピクルされていないオブジェクトには、 定義モジュールのインポート可能 が必要です。
通常は単純なテキストファイルであるため、ピクル処理されたファイルを編集することにより、モジュールパスを変更できます(つまり、tools
からwhyteboard.tools
に)。