2012-11-15 6 views
16

私は同僚が作成したプロジェクトProject Aを別のpythonプロジェクトに統合しようとしています。今、この同僚は彼のコード内で相対的な輸入を使用していないが、代わりに変更されたモジュールパスを持つPythonオブジェクトをピッキング解除する

from packageA.moduleA import ClassA 
from packageA.moduleA import ClassB 

を行って、その結果cPickleを持つクラスを漬け。きちんとしているので、彼のプロジェクト(Project A)が私のプロジェクトの中に構築されたパッケージを隠したいと思っています。ただし、これはpackageAで定義されたクラスのパスを変更します。いいえ問題は、私はちょうど

from ..packageA.moduleA import ClassA 
from ..packageA.moduleA import ClassB 

を使用してインポートが、今のクラスは、なぜ明らかモジュールDEFSを見ていcPickle

with open(fname) as infile: self.clzA = cPickle.load(infile) 
ImportError: No module named packageA.moduleA 

したがって、次のメッセージで失敗し、未酸洗を再定義していないでしょう。 packageAのルートをシステムパスに追加する必要がありますか?これは問題を解決する正しい方法ですか?

cPickledファイルが古いプロジェクト階層は、私がWrapperPackage

MyPackage/ 
.. __init__.py 
.. myModuleX.py 
.. myModuleY.py 
WrapperPackage/ 
.. __init__.py 
.. packageA/ 
    .. __init__.py 
    .. moduleA.py 
    .. moduleB.py 
.. packageB/ 
    .. __init__.py 
    .. moduleC.py 
    .. moduleD.py 
+0

私はKRunner用のプラグインを書いて、この問題に出くわしました。 Plasmaで使用されているスクリプトエンジンは、パスフックを使用して、自分のコードがある偽のパッケージを作成しました。 残念ながら、私はこれを解決する方法を見つけることができませんでした。私ができる唯一のことは、パスフックを手動で削除し、 'sys'キャッシュをクリアしてすべてを再インポートすることでした。しかし、もしあなたがいくつかのピクルされたデータを持っているなら、あなたは同じクラス名でそれをunpickleする必要があります(つまり 'from packageA.moduleA import ClassA'を保つ必要があります)。 一度アンピールしたら、正しい名前を使用してピッカーを再ピックすることができます。 – Bakuriu

答えて

15
にそのすべてを入れたいのですが、ソート

packageA/ 
    __init__.py 
    moduleA.py 
    moduleB.py 
packageB/ 
    __init__.py 
    moduleC.py 
    moduleD.py 

である

ccopy_reg 
_reconstructor 
p1 
(cpackageA.moduleA 
ClassA 
p2 
c__builtin__ 
object 
p3 
NtRp4 

ようになります

pickleインポートのエイリアスを作成する必要があります。 WrapperPackageパッケージの__init__.pyファイルに以下:

from .packageA import * # Ensures that all the modules have been loaded in their new locations *first*. 
from . import packageA # imports WrapperPackage/packageA 
import sys 
sys.modules['packageA'] = packageA # creates a packageA entry in sys.modules 

それはあなたがが、追加のエントリを作成する必要がありますということがあります

sys.modules['packageA.moduleA'] = moduleA 
# etc. 

今はcPickleは、古いで再びpackageA.moduleApackageA.moduleBがあります場所。

ピクルファイルを後で書き直したい場合は、その時点で新しいモジュールの場所が使用されます。上記で作成された追加のエイリアスは、問題のモジュールがcPickleの新しいロケーション名を持っていることを確実にして、クラスを再度書き込むときに有効にする必要があります。

+0

'WrapperPackege .__ init __。py'でこれを行う必要がありますか? –

+0

@MattiLyra:これはどこでも実行できますが、おそらく 'WrapperPackage/__ init __。py'ファイルが最適な場所です。 –

+0

@MartinPieters PEP328によると、 'import .something'は無効です。' from .something import module'ですか? 'import .something'は' SyntaxError'をスローしますか? http://www.python.org/dev/peps/pep-0328/#guido-s-decision –

4

@MartinPietersに加えてこれを行う別の方法は、cPickle.Unpicklerクラスのfind_globalメソッドを定義するか、またはpickle.Unpicklerクラスを拡張することです。

def map_path(mod_name, kls_name): 
    if mod_name.startswith('packageA'): # catch all old module names 
     mod = __import__('WrapperPackage.%s'%mod_name, fromlist=[mod_name]) 
     return getattr(mod, kls_name) 
    else: 
     mod = __import__(mod_name) 
     return getattr(mod, kls_name) 

import cPickle as pickle 
with open('dump.pickle','r') as fh: 
    unpickler = pickle.Unpickler(fh) 
    unpickler.find_global = map_path 
    obj = unpickler.load() # object will now contain the new class path reference 

with open('dump-new.pickle','w') as fh: 
    pickle.dump(obj, fh) # ClassA will now have a new path in 'dump-new' 

picklecPickleの両方のためのプロセスのより詳細な説明はhereを見出すことができます。

+0

リンク先のウェブサイトはオフラインですが、archive.orgにはこちら(https://web-beta.archive.org/web/20130423223601/http://nadiana.com/python-pickle-insecure)があります読める価値がある –

2

可能な解決策の1つは、ピクルスファイルを直接編集することです(アクセス権がある場合)。私は変更されたモジュールパスのこの同じ問題に遭遇し、ファイルをpickle.HIGHEST_PROTOCOLとして保存したので、理論上はバイナリでなければならないが、モジュールパスはプレーンテキストのpickleファイルの先頭に置かれていた。だから、私はちょうど、古いモジュールのパスのすべてのインスタンスを新しいものと置き換えて見つけました。そして、正しくロードされました。

非常に複雑なピクルオブジェクトを使用している場合は、このソリューションは誰にとっても魅力的ではないと確信していますが、すばやく汚れたデータフィックスです。

0

これは、曖昧さのない迅速な遷移マップを使用した、柔軟なunpicklingのための私の基本パターンです。通常、酸洗いに関連するプリミティブデータ型に加えて、いくつかの既知のクラスがあります。これはまた、間違いやすいデータや悪意のあるデータからのunpickleを防ぎます。ちなみに、単純なpickle.load()(エラーが発生しやすいsys.modulesの飛ばしがあってもいなくても)で任意のPythonコード(!)を実行できます。

Pythonの2 & 3:

from __future__ import print_function 
try: import cPickle as pickle, copy_reg as copyreg 
except: import pickle, copyreg 

class OldZ: 
    a = 1 
class Z(object): 
    a = 2 
class Dangerous: 
    pass 

_unpickle_map_safe = { 
    # all possible and allowed (!) classes & upgrade paths  
    (__name__, 'Z')   : Z,  
    (__name__, 'OldZ')  : Z, 
    ('old.package', 'OldZ') : Z, 
    ('__main__', 'Z')  : Z, 
    ('__main__', 'OldZ') : Z, 
    # basically required 
    ('copy_reg', '_reconstructor') : copyreg._reconstructor,  
    ('__builtin__', 'object')  : copyreg._reconstructor,  
    } 

def unpickle_find_class(modname, clsname): 
    print("DEBUG unpickling: %(modname)s . %(clsname)s" % locals()) 
    try: return _unpickle_map_safe[(modname, clsname)] 
    except KeyError: 
     raise pickle.UnpicklingError(
      "%(modname)s . %(clsname)s not allowed" % locals()) 
if pickle.__name__ == 'cPickle': # PY2 
    def SafeUnpickler(f): 
     u = pickle.Unpickler(f) 
     u.find_global = unpickle_find_class 
     return u 
else: # PY3 & Python2-pickle.py 
    class SafeUnpickler(pickle.Unpickler): 
     find_class = staticmethod(unpickle_find_class) 

def test(fn='./z.pkl'): 
    z = OldZ() 
    z.b = 'teststring' + sys.version 
    pickle.dump(z, open(fn, 'wb'), 2) 
    pickle.dump(Dangerous(), open(fn + 'D', 'wb'), 2) 
    # load again 
    o = SafeUnpickler(open(fn, 'rb')).load() 
    print(pickle, "loaded:", o, o.a, o.b) 
    assert o.__class__ is Z 
    try: raise SafeUnpickler(open(fn + 'D', 'rb')).load() and AssertionError 
    except pickle.UnpicklingError: print('OK: Dangerous not allowed') 

if __name__ == '__main__': 
    test() 
関連する問題