使用更改的模块路径取消python对象

时间:2012-11-15 13:29:27

标签: python import pickle

我正在尝试将同事构建的项目Project A集成到另一个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

但是现在unle pickle这些类失败并显示以下消息

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

那么为什么cPickle显然没有看到模块defs。我是否需要将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

4 个答案:

答案 0 :(得分:18)

您需要为pickle导入创建一个别名才能工作;以下是__init__.py包的WrapperPackage文件:

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的新位置名称。

答案 1 :(得分:4)

除了@MartinPieters,回答另一种方法是定义find_global类的cPickle.Unpickler方法,或扩展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

答案 2 :(得分:2)

一种可能的解决方案是直接编辑pickle文件(如果您有权访问)。我遇到了更改模块路径的同样问题,我将文件保存为pickle.HIGHEST_PROTOCOL,因此它理论上应该是二进制文件,但模块路径以纯文本形式位于pickle文件的顶部。所以我只是用新的模块路径替换旧模块路径的所有实例,然后正确加载它们。

我确信这个解决方案不适合所有人,特别是如果你有一个非常复杂的pickle对象,但它是一个快速而肮脏的数据修复,对我有用!

答案 3 :(得分:0)

这是我灵活拆解的基本模式 - 通过明确且快速的转换映射 - 因为除了与酸洗相关的原始数据类型之外,通常只有一些已知的类。这也可以保护unpickling免受错误或恶意构造的数据的攻击,这些数据毕竟可以在简单的pickle.load()上执行任意python代码(!)(有或没有容易出错的sys.modules)。

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()