我正在尝试将同事构建的项目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
答案 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.moduleA
和packageA.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'
可以找到pickle
和cPickle
的详细解释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()