2012-11-15 11 views
16

Sto cercando di integrare un progetto Project A creato da un collega in un altro progetto python. Ora, questo collega non ha utilizzato le importazioni relative nel suo codice, ma invece fattoEliminare oggetti python con un percorso modulo modificato

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

e di conseguenza in salamoia le classi con cPickle. Per comodità vorrei nascondere il pacchetto che il suo (Project A) ha costruito all'interno del mio progetto. Ciò tuttavia modifica il percorso delle classi definite in packageA. Nessun problema, mi limiterò a ridefinire l'importazione utilizzando

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

ma ora il decapaggio delle Nazioni Unite le classi non riesce con il seguente messaggio

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

Allora perché non cPickle non pare vedere i defs modulo. Devo aggiungere la radice di packageA al percorso di sistema? È questo il modo corretto per risolvere il problema?

Il file cPickled simile a

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

La gerarchia vecchio progetto è del tipo

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

mi piacerebbe mettere tutto questo in 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 
+0

mi sono imbattuto in questo problema di scrittura di un plug-in per KRunner. Il motore di script usato da Plasma utilizzava un hook di percorso per creare un pacchetto falso dove era il mio codice. Purtroppo non sono riuscito a trovare alcun modo per risolvere questo. L'unica cosa che potevo fare era rimuovere manualmente il loro hook di percorso, cancellare le cache 'sys' e reimportare tutto. Ma se si hanno alcuni dati in pickled, è necessario annullarlo con lo stesso nome di classe (il che significa che è necessario mantenere 'da packageA.moduleA import ClassA'). Nota che, una volta deselezionati, puoi reiscriverli utilizzando il nome corretto. – Bakuriu

risposta

15

Avrai bisogno di creare un alias per l'importazione pickle per funzionare; il seguente al file __init__.py del pacchetto 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 

Può essere che è necessario creare ulteriori voci però:

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

Ora cPickle troverà packageA.moduleA e packageA.moduleB di nuovo al loro vecchio posizioni.

Si consiglia di riscrivere il file pickle in seguito, la nuova posizione del modulo verrà utilizzata in quel momento. Gli alias aggiuntivi creati sopra devono garantire che i moduli in questione abbiano il nuovo nome di posizione per cPickle da prelevare quando si scrivono di nuovo le classi.

+0

Devo farlo in 'WrapperPackege .__ init __. Py'? –

+0

@MattiLyra: Puoi farlo ovunque, ma il file 'WrapperPackage/__ init __. Py' è probabilmente il posto migliore. –

+0

@MartinPieters Secondo PEP328 'import .qualcosa' non è valido, deve essere' 'dal modulo di importazione .something'? 'import .something' genera un' SyntaxError'? http://www.python.org/dev/peps/pep-0328/#guido-s-decision –

4

Oltre alla risposta @MartinPieters, l'altro modo consiste nel definire il metodo find_global della classe cPickle.Unpickler o estendere la classe 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 spiegazione più dettagliata del processo sia per pickle e cPickle può essere trovato here.

+0

Il sito Web collegato non è in linea, ma archive.org lo ha [qui] (https://web-beta.archive.org/web/20130423223601/http://nadiana.com/python-pickle-insecure), e vale la pena leggerlo –

2

Una possibile soluzione è modificare direttamente il file pickle (se si dispone dell'accesso). Mi sono imbattuto in questo stesso problema di un percorso del modulo modificato, e avevo salvato i file come pickle.HIGHEST_PROTOCOL quindi dovrebbe essere in teoria binario, ma il percorso del modulo era seduto nella parte superiore del file pickle in testo normale. Così ho appena fatto un rimpiazzo di ricerca su tutte le istanze del vecchio percorso del modulo con quello nuovo e voilà, hanno caricato correttamente.

Sono sicuro che questa soluzione non è adatta a tutti, soprattutto se si dispone di un oggetto pickled molto complesso, ma è una soluzione rapida e sporca che ha funzionato per me!

0

Questo è il mio modello di base per il disfacimento flessibile - tramite una mappa di transizione non ambigua e veloce - poiché di solito esistono solo poche classi note oltre ai tipi di dati primitivi rilevanti per il decapaggio. Ciò protegge anche il disimpegno da dati errati o costruiti maliziosamente, che dopo tutto possono eseguire codice python arbitrario (!) Su un semplice pickle.load() (con o senza sys.modules soggetto a errore che armeggia).

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