2012-11-15 4 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 

를 사용하여 가져 오기하지만 지금은 클래스 이유를 분명히 모듈 인증 된 정의가 표시되지 cPickle 않습니다

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

그래서 다음과 같은 메시지와 함께 실패 않은 산세를 다시 정의하지 것이다. packageA의 루트를 시스템 경로에 추가해야합니까? 이것이 문제를 해결하는 올바른 방법입니까?

cPickled 파일

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

오래된 프로젝트 계층 구조 같은 것이

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

가 나는 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
에이 모든 것을 넣어하고자하는 종류의 외모

피클 불러 오기가 작동하려면 별칭을 만들어야합니다. 하여 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를 찾을 수 위치.

나중에 pickle 파일을 다시 쓰고 싶을 때 새로운 모듈 위치가 그 시간에 사용됩니다. 위에 추가 된 별칭을 사용하면 문제의 모듈이 cPickle에 대한 새 위치 이름을 갖도록해야 클래스를 다시 작성할 수 있습니다.

+0

'WrapperPackege .__ init __. py'에서이 작업을 수행해야합니까? –

+0

@MattiLyra : 어디서나이 작업을 수행 할 수 있지만,'WrapperPackage/__ init __. py' 파일이 아마도 가장 좋은 장소 일 것입니다. –

+0

@MartinPieters PEP328에 따르면'import .something '은 유효하지 않으므로'from .simething 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

가능한 해결책은 pickle 파일을 직접 편집하는 것입니다 (액세스 가능한 경우). 변경된 모듈 경로와 동일한 문제가 발생하여 pickle.HIGHEST_PROTOCOL 파일을 이론적으로 이진 파일로 저장했지만 모듈 경로는 피클 파일의 맨 위에 일반 텍스트로 저장되어있었습니다. 그래서 이전 모듈 경로의 모든 인스턴스를 새로운 모듈 경로로 바꾸고 바르게로드했습니다.

나는이 솔루션이 모든 사람에게 적합하지는 않을 것이라고 확신합니다. 특히 피클 링 개체가 매우 복잡한 경우에는 그렇습니다. 그러나 나를 위해 신속하고 더러운 데이터 수정이 필요합니다.

0

이것은 절어와 관련된 원시 데이터 유형 외에 일반적으로 알려진 클래스가 거의 없으므로 모호하지 않고 빠른 전환 맵을 통해 유연한 unpickling을위한 기본 패턴입니다. 이것은 또한 오류가 있거나 악의적으로 생성 된 데이터에 대한 unpickle을 보호합니다. 결국 간단한 pickle.load() (오류가 발생하기 쉬운 sys.modules의 조작이 있거나없는 경우)에서 임의의 파이썬 코드 (!)를 실행할 수 있습니다.

파이썬 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()