2012-08-22 12 views
7

Desidero implementare il supporto del decapaggio per gli oggetti appartenenti alla mia libreria di estensioni. Esiste un'istanza globale di servizio di classe inizializzata all'avvio. Tutti questi oggetti sono prodotti come risultato di alcune invocazioni del metodo di servizio e in sostanza ne fanno parte. Il servizio sa come serializzarli in buffer binari e come deserializzare i buffer in oggetti.__reduce __/copy_reg python's unpickler semantico e statefull

Sembra che Pythons __ reduce__ debba servire al mio scopo: implementare il supporto per il decapaggio. Ho iniziato a implementarne uno e mi sono reso conto che c'è un problema con unpickler (primo elemento di una tupla che dovrebbe essere restituita da __ reduce__). Questa funzione di unpickle richiede l'istanza di un servizio per poter convertire il buffer di input in un oggetto. Ecco un po 'di pseudo codice per illustrare il problema:

class Service(object): 
    ... 
    def pickleObject(self,obj): 
     # do serialization here and return buffer 
     ... 

    def unpickleObject(self,buffer): 
     # do deserialization here and return new Object 
     ... 

class Object(object): 
    ... 
    def __reduce__(self): 
     return self.service().unpickleObject, (self.service().pickleObject(self),) 

Nota il primo elemento in una tupla. Il pickler Python non gli piace: dice che è instancemethod e non può essere decapitato. Ovviamente il pickler sta cercando di memorizzare la routine nell'output e vuole l'istanza del servizio insieme al nome della funzione, ma questo non è ciò che voglio che accada. Non voglio (e davvero non posso: il servizio non è selezionabile) per archiviare il servizio insieme a tutti gli oggetti. Voglio che l'istanza di servizio venga creata prima che pickle.load venga invocato e in qualche modo quell'istanza venga utilizzata durante l'annullamento.

Qui dove sono venuto dal modulo copy_reg. Di nuovo è apparso come dovrebbe risolvere i miei problemi. Questo modulo consente di registrare dinamicamente le routine di pickler e unpickler per tipo e queste dovrebbero essere usate in seguito per gli oggetti di questo tipo. Così ho aggiunto questa registrazione alla costruzione di servizi:

class Service(object): 
    ... 
    def __init__(self): 
     ... 
     import copy_reg 
     copy_reg(mymodule.Object, self.pickleObject, self.unpickleObject) 

self.unpickleObject è ora un balzo servizio di metodo prendendo come primo parametro e buffer come secondo. self.pickleObject è anche un metodo vincolato che prende servizio e oggetto in pickle. copy_reg richiede che la routine pickleObject debba seguire la semantica del riduttore e restituisce tupla simile come in precedenza. E qui il problema è sorto di nuovo: cosa dovrei restituire come il primo elemento tuple ??

class Service(object): 
    ... 
    def pickleObject(self,obj): 
     ... 
     return self.unpickleObject, (self.serialize(obj),) 

In questa forma sottaceto si lamenta ancora di non poter decapitare l'instancemethod. Ho provato None - non mi piace neanche. Ho messo lì una funzione fittizia. Funziona - il che significa che la fase di serializzazione è andata bene, ma durante l'annullamento chiama questa funzione fittizia invece di unpuncolare che ho registrato per il tipo mymodule.Object in Service constructor.

Quindi ora sono in perdita. Scusa per una lunga spiegazione: non sapevo come fare questa domanda in poche righe. Posso riassumere le mie domande come questa:

  1. Perché il copy_reg semantica mi obbliga a tornare di routine unpickler da pickleObject, se mi aspettavo di un registro unico modo indipendente?
  2. C'è qualche motivo per preferire l'interfaccia copy_reg.constructor per registrare la routine unpickler?
  3. Come faccio a rendere pickle per utilizzare il unpickler che ho registrato al posto di uno all'interno del flusso?
  4. Cosa devo restituire come primo elemento in una tupla come valore di risultato pickleObject? Esiste un valore "corretto"?
  5. Mi avvicino al tutto correttamente? C'è una soluzione diversa/più semplice?

Grazie per il vostro tempo.

Gennadiy

risposta

3

Prima di tutto, il modulo copy_reg è improbabile che aiuterà molto qui: è soprattutto un modo per aggiungere __reduce__ come le caratteristiche di classi che non hanno lo stesso metodo, piuttosto che offrire le abilità speciali (ad es. se vuoi mettere sott'occhio oggetti da qualche libreria che non lo supporta in modo nativo).

Il callable restituito da __reduce__ deve essere localizzabile nell'ambiente in cui l'oggetto deve essere deselezionato, quindi un metodo di istanza non è realmente appropriato. Come accennato nella Pickle documentation:

Nell'ambiente deserializzazione questo oggetto deve essere o una classe, un richiamabile registrato come “costruttore sicuro” (vedi sotto), o deve avere un attributo __safe_for_unpickling__ con un vero valore.

Quindi, se è stata definita una funzione (non metodo) come segue:

def _unpickle_service_object(buffer): 
    # Grab the global service object, however that is accomplished 
    service = get_global_service_object() 
    return service.unpickleObject(buffer) 

_unpickle_service_object.__safe_for_unpickling__ = True 

Si può ora utilizzare questa funzione _unpickle_service_object nel valore di ritorno dei tuoi __reduce__ metodi in modo che gli oggetti legati al nuovo ambiente di globale Service oggetto non specificato.

+0

Non hai risposto Q1 sopra. –

+0

Ho finito per fare qualcosa di simile. Anche se ho chiamato l'istanza di servizio globale, questo non è esattamente corretto. In effetti, esiste solo un'istanza utilizzata nell'applicazione di produzione, ma nulla dice che sia globale. E in effetti nei miei test unitari continuo a crearne uno nuovo ancora e ancora. Ho implementato un modulo pickle.py nel mio pacchetto con il registro di routine def (svc) e il servizio a livello di modulo globale variabile che viene impostato nel registro. Anche questa routine registra le funzioni di decapaggio/annullamento utilizzando copy_reg. Questa routine viene richiamata dal costruttore del servizio. Esistono tuttavia alcuni problemi: –

+0

1. Funziona solo per una singola istanza di servizio. In altre parole, non possiamo avere due servizi diversi esistenti simultaneamente ed entrambi hanno oggetti selezionabili 2. Questo riferimento globale è fonte di potenziale perdita di memoria. Come e quando ho bisogno di ripulirlo? –

Problemi correlati