Descomprimir objetos de Python con una ruta de módulo modificada

Estoy tratando de integrar un proyecto Project A construido por un colega en otro proyecto de python. Ahora este colega no ha usado importaciones relativas en su código sino que ha hecho

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

y en consecuencia cPickle las clases con cPickle . Por pulcritud me gustaría ocultar el paquete que su ( Project A ) construyó dentro de mi proyecto. Sin embargo, esto cambia la ruta de las clases definidas en packageA . No hay problema, solo redefiniré la importación usando

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

pero ahora la ONU decapado las clases falla con el siguiente mensaje

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

Entonces, ¿por qué cPickle aparentemente no ve las definiciones del módulo? ¿Necesito agregar la raíz del packageA a la ruta del sistema? ¿Es esta la manera correcta de resolver el problema?

El archivo cPickled ve algo así como

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

La antigua jerarquía de proyectos es del tipo.

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

Me gustaría poner todo eso en un 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 

Tendrá que crear un alias para que funcione la importación de pickle; lo siguiente al archivo WrapperPackage paquete 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 

Puede ser que necesites crear entradas adicionales aunque:

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

Ahora cPickle encontrará packageA.moduleA y packageA.moduleB nuevamente en sus ubicaciones anteriores.

Es posible que desee volver a escribir el archivo pickle después, la ubicación del nuevo módulo se utilizará en ese momento. Los alias adicionales creados anteriormente deben garantizar que los módulos en cuestión tengan el nuevo nombre de ubicación para que cPickle al escribir las clases nuevamente.

Además de que @MartinPieters responde, la otra forma de hacerlo es definir el método find_global de la clase cPickle.Unpickler , o extender la clase 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' 

Una explicación más detallada del proceso para pickle y cPickle se puede encontrar aquí .

Una posible solución es editar directamente el archivo pickle (si tiene acceso). Me encontré con el mismo problema de una ruta de módulo modificada, y había guardado los archivos como pickle.HIGHEST_PROTOCOL, por lo que debería ser binario en teoría, pero la ruta del módulo estaba en la parte superior del archivo de pickle en texto plano. Así que acabo de hacer un reemplazo de búsqueda en todas las instancias de la ruta del módulo anterior con la nueva y listo, se cargaron correctamente.

Estoy seguro de que esta solución no es para todos, especialmente si tiene un objeto encurtido muy complejo, ¡pero es una solución de datos rápida y sucia que funcionó para mí!

Este es mi patrón básico para descifrar de manera flexible, a través de un mapa de transición rápido y sin ambigüedades, ya que generalmente hay solo unas pocas clases conocidas además de los tipos de datos primitivos relevantes para el decapado. Esto también protege la eliminación de datos erróneos o malintencionados, que, después de todo, pueden ejecutar un código Python arbitrario (!) En un simple pickle.load() (con o sin problemas de sys.modules propensos a errores).

Python 2 y 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() zb = '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, oa, ob) 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()