2014-11-02 1 views
6

Vorrei passare lo stato oggetto tra due programmi Python (uno è il mio codice in esecuzione standalone, uno è una vista Pyramid) e diversi spazi dei nomi. Le domande un po 'correlate sono here o here, ma non riesco a seguirle abbastanza per il mio scenario.Archiviare oggetto usando Pickle Python e caricarlo in uno spazio dei nomi diverso

mio codice definisce una classe globale (cioè __main__ namespace) di struttura piuttosto complexish:

# An instance of this is a colorful mess of nested lists and sets and dicts. 
class MyClass : 
    def __init__(self) : 
     data = set() 
     more = dict() 
     ... 

    def do_sth(self) : 
     ... 

A un certo punto pickle un'istanza di questa classe:

c = MyClass() 
# Fill c with data. 

# Pickle and write the MyClass instance within the __main__ namespace. 
with open("my_c.pik", "wb") as f : 
    pickle.dump(c, f, -1) 

A hexdump -C my_c.pik mostra che la prima coppia di byte contiene __main__.MyClass da cui presumo che la classe sia effettivamente definita nel namespace globale e che questo sia in qualche modo un requisito per leggere il pickle. Ora vorrei caricare questo salamoia MyClass esempio all'interno di una piramide vista, che presumo è uno spazio dei nomi diverso:

# In Pyramid (different namespace) read the pickled MyClass instance. 
with open("my_c.pik", "rb") as f : 
    c = pickle.load(f) 

Ma che genera il seguente errore:

File ".../views.py", line 60, in view_handler_bla 
    c = pickle.load(f) 
AttributeError: 'module' object has no attribute 'MyClass' 

Sembra me che la definizione MyClass manca in qualsiasi spazio dei nomi che viene eseguito dal codice di visualizzazione? Speravo (presumevo) che il decapaggio fosse un processo un po 'opaco che mi permettesse di leggere un blob di dati nel luogo che ho scelto. (Maggiori informazioni sui nomi delle classi e gli spazi dei nomi di Python sono here.)

Come posso gestirlo correttamente? (Idealmente senza dover importare cose attraverso ...) Posso in qualche modo trovare lo spazio dei nomi corrente e iniettare MyClass (come sembra suggerire la risposta this)?

Poor Soluzione

Mi sembra che se mi astengo dal definire e utilizzare MyClass e invece ricadere a semplici tipi di dati built-in, questo non sarebbe un problema. Infatti, ho potuto "serializzare" l'oggetto MyClass in una sequenza di chiamate che Pickle i singoli elementi dell'istanza MyClass:

# 'Manual' serialization of c works, because all elements are built-in types. 
pickle.dump(c.data, f, -1) 
pickle.dump(c.more, f, -1) 
... 

Questo sarebbe annullare l'obiettivo di incarto dati in classi però.

Nota

decapaggio si occupa solo dello stato di una classe, non di eventuali funzioni definite nell'ambito della classe (es do_sth() nell'esempio precedente). Ciò significa che il caricamento di un'istanza MyClass in uno spazio dei nomi diverso senza la definizione di classe appropriata carica solo i dati dell'istanza; chiamare una funzione mancante come do_sth() causerà un AttributeError.

+0

hai pensato di usare [tuple di nome] (http: // StackOverflow. com/domande/2970608/cosa-sono-nome-tuple-in-Python)? Dovrebbero essere anche selezionabili. – User

+1

@User: Sarebbe parte della "soluzione scadente" sopra la quale ricado ai tipi più semplici. Sì, l'ho provato e funziona. Ma questa è una soluzione alternativa evitante, non una risposta alla mia domanda ;-) – Jens

+0

Potresti semplicemente definire 'MyClass' in un modulo diverso da' __main__' ... il tuo primo programma dovrebbe usare qualche modulo personalizzato per definire quella classe, e poi devi solo aggiungerlo al percorso per il secondo programma. – Bakuriu

risposta

1

Soluzione 1

Sulla pickle.load, il modulo __main__ deve avere una funzione o una classe denominata MyClass.Questo non ha bisogno di essere la classe originale con il codice sorgente originale. Puoi inserire altri metodi. Dovrebbe funzionare.

class MyClass(object): 
    pass 

with open("my_c.pik", "rb") as f : 
    c = pickle.load(f) 

Soluzione 2

Utilizza la copyreg module che viene utilizzato per registrare costruttori e funzioni salamoia decapare oggetti specifici. Questo è l'esempio di modulo di un numero complesso:

def pickle_complex(c): 
    return complex, (c.real, c.imag) 

copyreg.pickle(complex, pickle_complex, complex) 

Soluzione 3

Override la persistent_id method del Pickler e Unpickler. pickler.persistent_id(obj) restituirà un identificatore che può essere risolto da unpickler.persistent_id(id) all'oggetto.

+1

Non ho (voglio toccare) il codice Pyramid. Ma la tua risposta va di pari passo con la risposta [this] (http://stackoverflow.com/questions/1947904/how-can-i-pickle-a-ested-class-in-python#1948057): iniettare un simbolo e (senza significato) informazioni sul tipo. Infatti, quando aggiungo 'setattr (sys.modules [" __ main__ "]," MyClass ", tipo (MyClass()))' prima della chiamata 'pickle.load()', tutto sembra funzionare. Si noti che 'MyClass' non ha bisogno di essere definito da nessuna parte. Mi sento piuttosto * hackish * per me ... :-) – Jens

+0

C'è una soluzione anche a questo. Si chiama 'persistent_id'. Questo metodo deve essere sovrascritto dalle sottoclassi di 'Pickler' e' Unpickler'. Permette di restituire gli ID e risolverli. In alternativa puoi usare il modulo 'copyreg'. Date un'occhiata a questo. – User

+0

Grazie per i tre suggerimenti. Qual è il più pulito e il più pignolo? Sembra che entrambe le soluzioni 2 e 3 richiedano il trasferimento di funzionalità aggiuntive tra i due pezzi di codice Python per garantire che le istanze di 'MyClass' possano essere decapitate/non compilate tra i namespace ... – Jens

10

Utilizzare dill invece di pickle, perché dill per default sottaceti serializzando la definizione di classe e non per riferimento.

>>> import dill 
>>> class MyClass: 
... def __init__(self): 
...  self.data = set() 
...  self.more = dict() 
... def do_stuff(self): 
...  return sorted(self.more) 
... 
>>> c = MyClass() 
>>> c.data.add(1) 
>>> c.data.add(2) 
>>> c.data.add(3) 
>>> c.data 
set([1, 2, 3]) 
>>> c.more['1'] = 1 
>>> c.more['2'] = 2 
>>> c.more['3'] = lambda x:x 
>>> def more_stuff(self, x): 
... return x+1 
... 
>>> c.more_stuff = more_stuff 
>>> 
>>> with open('my_c.pik', "wb") as f: 
... dill.dump(c, f) 
... 
>>> 

Arrestare la sessione, e riavviare in una nuova sessione ...

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import dill 
>>> with open('my_c.pik', "rb") as f: 
... c = dill.load(f) 
... 
>>> c.data 
set([1, 2, 3]) 
>>> c.more 
{'1': 1, '3': <function <lambda> at 0x10473ec80>, '2': 2} 
>>> c.do_stuff() 
['1', '2', '3'] 
>>> c.more_stuff(5) 
6 

Get dill qui: https://github.com/uqfoundation/dill

+0

E un oggetto "dilled" (con la sua classe) può essere quindi riletto in qualsiasi spazio dei nomi? – Jens

+2

Sì. Può anche essere riletto nello stesso spazio dei nomi in cui la definizione della classe è cambiata e funziona ancora. –

+0

Ho appena aggiunto una nota alla domanda originale riguardante le funzioni di una classe: non sono * decapitati *; solo lo stato è. Sembra che [dill] (http://trac.mystic.cacr.caltech.edu/project/pathos/browser/dill/tests) includa effettivamente le funzioni definite all'interno dell'ambito 'class'? Quindi un'istanza di classe "dilled" può quindi essere caricata in un namespace diverso * e * è possibile chiamare le sue funzioni (senza dover includere il modulo)? – Jens