Unpickling объекты python с измененным пути модуля
Я пытаюсь интегрировать проект 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
но теперь un pickling классы терпит неудачу со следующим сообщением
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
Ответы
Ответ 1
Вам нужно создать псевдоним для импорта рассола для работы; следующее в __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
, которое будет отображаться при повторном написании классов.
Ответ 2
В дополнение к @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
можно найти здесь.
Ответ 3
Одним из возможных решений является прямое редактирование файла pickle (если у вас есть доступ). Я столкнулся с той же проблемой измененного пути к модулю, и я сохранил файлы как pickle.HIGHEST_PROTOCOL, поэтому он должен быть двоичным в теории, но путь к модулю находился в верхней части файла pickle в виде обычного текста. Поэтому я просто заменил find на всех экземплярах старого пути модуля новым и voila, они загрузились правильно.
Я уверен, что это решение не для всех, особенно если у вас очень сложный маринованный объект, но это быстрое и грязное исправление данных, которое сработало для меня!
Ответ 4
Это мой основной шаблон для гибкой раскраски - через однозначную и быструю карту перехода - так как обычно есть несколько известных классов, кроме примитивных типов данных, релевантных для травления. Это также защищает от нежелательных или вредоносных данных, которые в конце концов могут выполнять произвольный код на Python (!) На простой pickle.load()
(с ошибкой или без ошибок при запуске 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()