2011-01-10 17 views
13

Ho un sistema che memorizza comunemente i tipi di classi decapitati.Sottoclasse sottoclasse con parametri dinamici

Voglio essere in grado di salvare le classi con parametri dinamici allo stesso modo, ma non posso perché ottengo un PicklingError nel tentativo di decollare una classe che non è globalmente trovata (non definita nel codice semplice).

Il mio problema può essere modellato come il seguente codice di esempio:

class Base(object): 
def m(self): 
    return self.__class__.PARAM 

def make_parameterized(param_value): 
class AutoSubClass(Base): 
    PARAM = param_value 
return AutoSubClass 

cls = make_parameterized(input("param value?")) 

Quando provo fare la serializzazione di classe, ottengo il seguente errore:

# pickle.PicklingError: Can't pickle <class '__main__.AutoSubClass'>: it's not found as __main__.AutoSubClass 
import pickle 
print pickle.dumps(cls) 

Sto cercando un metodo per dichiarare Base come ParameterizableBaseClass che dovrebbe definire i parametri necessari (PARAM nell'esempio sopra). Una sottoclasse parametrica dinamica (cls precedente) dovrebbe quindi essere selezionabile salvando il tipo "ParameterizableBaseClass" ei diversi valori param (dinamico param_value sopra).

Sono sicuro che in molti casi questo può essere evitato del tutto ... E posso evitarlo anche nel mio codice se davvero (davvero) devo. Stavo giocando con __metaclass__, copyreg e anche __builtin__.issubclass ad un certo punto (non chiedere), ma non è stato in grado di rompere questo.

Mi sento come se non dovessi essere fedele allo spirito pitone se non dovessi chiedere: come si può raggiungere questo, in modo relativamente pulito?

+1

In modo relativamente pulito? Non penso che sia possibile per qualsiasi definizione ragionevole di "pulito". Perché stai provando a decapitare oggetti le cui classi sono generate in fase di esecuzione? Qual è il vero caso d'uso? Come hai intenzione di disimpegnare qualcosa la cui classe non esiste? –

+1

Anche io non capisco il caso d'uso per questo. Perché non puoi semplicemente restituire un'istanza della classe con quel parametro come attributo dopo l'inizializzazione. Un altro commento, dovresti anche evitare la magia di python, ad esempio self .__ class __. PARAM'. Penso che ti stia complicando troppo. – milkypostman

+2

Può essere il caso che questa sia una complicazione eccessiva (nel qual caso la domanda potrebbe ancora rimanere, anche senza riguardo al mio caso). Il mio esempio probabilmente sembra davvero stupido - è solo un esempio. Il mio sistema reale sottaceta i tipi di classe insieme ai parametri di creazione e li passa tra i processi in uno scenario di elaborazione parallela. Oltre a ~ 20 classi diverse in uso, con codice specifico in ognuna di esse, ho alcune classi 'basta scegliere il parametro'. Voglio essere in grado di suddividerle in modo dinamico, senza bisogno di cambiare (e potenzialmente ricaricare) il codice e renderle selezionabili. HTH. – Yonatan

risposta

4

Sì, è possibile -

Ogni volta che si vuole su misura il Pickle e comportamenti deserializzazione per gli oggetti, basta impostare i "__getstate__" e "__setstate__" metodi sulla classe stessa.

In questo caso è un po 'più complicato: È necessario, come è stato osservato, che esista una classe nello spazio dei nomi globale che è la classe dell'oggetto attualmente sottoposto a pickled: deve essere la stessa classe, con stesso nome. Ok - l'accordo è che questa classe esistente nello spazio del nome globale può essere creata al momento di Pickle.

A Unpickle time la classe, con lo stesso nome, deve esistere - ma non deve essere lo stesso oggetto - si comporta semplicemente come fa - e come __setstate__ viene chiamato nel processo Unpickling, può ricreare il classe parametrizzata dell'oggetto originale e impostare la propria classe come quella, impostando l'attributo __class__ dell'oggetto.

Impostare l'attributo __class__ di un oggetto può sembrare discutibile ma è come OO funziona in Python ed è ufficialmente documentato, funziona anche attraverso le implementazioni. (Ho provato questo frammento sia in Python 2.6 e PyPy)

class Base(object): 
    def m(self): 
     return self.__class__.PARAM 
    def __getstate__(self): 
     global AutoSub 
     AutoSub = self.__class__ 
     return (self.__dict__,self.__class__.PARAM) 
    def __setstate__(self, state): 
     self.__class__ = make_parameterized(state[1]) 
     self.__dict__.update(state[0]) 

def make_parameterized(param_value): 
    class AutoSub(Base): 
     PARAM = param_value 
    return AutoSub 

class AutoSub(Base): 
    pass 


if __name__ == "__main__": 

    from pickle import dumps, loads 

    a = make_parameterized("a")() 
    b = make_parameterized("b")() 

    print a.PARAM, b.PARAM, type(a) is type(b) 
    a_p = dumps(a) 
    b_p = dumps(b) 

    del a, b 
    a = loads(a_p) 
    b = loads(b_p) 

    print a.PARAM, b.PARAM, type(a) is type(b) 
+0

Immagino di avere solo io stesso la colpa che il trucco che stavo cercando di tirare è al massimo un hacker ... Il tuo metodo sembra il modo per ottenere ciò che è stato discusso. – Yonatan

+0

L'abilità qui è che tu fornisci comunque una definizione di classe globale, se solo fittizia: 'class AutoSub (Base): pass'. Ma carino. – ThomasH

1

Le classi non create nel livello superiore di un modulo non possono essere sottoposte a decapaggio, as shown in the Python documentation.

Inoltre, anche per un'istanza di una classe modulo di livello superiore gli attributi di classe non vengono memorizzati. Quindi nel tuo esempio PARAM non verrebbe comunque memorizzato. (Spiegato nella sezione di documentazione di Python linkata sopra)

+0

Grazie per la tua risposta! È vero che l'uso casuale di 'pickle.dump' non accetterà di decantare i miei tipi di classe. Sto cercando un meccanismo (come '__reduce__',' __setstate__' o simile in un costrutto di classe base o meta-classe) che mi consenta di decapitare una tupla _describing_ my class e ricostruire la mia classe in seguito. – Yonatan

2

Suppongo che sia troppo tardi ora, ma pickle è un modulo che preferisco evitare per qualcosa di complesso, perché ha problemi come questo e molti altri.

In ogni modo, dal momento che sottaceti vuole la classe in un globale può avere:

import cPickle 

class Base(object): 
    def m(self): 
     return self.__class__.PARAM 

    @classmethod 
    def make_parameterized(cls,param): 
     clsname = "AutoSubClass.%s" % param 
     # create a class, assign it as a global under the same name 
     typ = globals()[clsname] = type(clsname, (cls,), dict(PARAM=param)) 
     return typ 

cls = Base.make_parameterized('asd') 

import pickle 
s = pickle.dumps(cls) 

cls = pickle.loads(s) 
print cls, cls.PARAM 
# <class '__main__.AutoSubClass.asd'> asd 

Ma sì, probabilmente stai overcomplicating cose.

+0

Pensando alle istanze di tali classi che vengono messe sottochiave in altri moduli, probabilmente funzionerà quando proverai a mettere sottosopra le istanze di 'cls', dato che puoi essere sicuro che il class factory è stato eseguito. Ma non sono così sicuro di disfarsi di tali istanze (in altri moduli), poiché in quel caso l'assegnazione di 'globals() [clsname]' potrebbe non essere stata eseguita (pensate alla prossima esecuzione del programma quando vuole leggere nuovamente le istanze in salamoia). – ThomasH

8

So che questa è una domanda molto vecchio, ma penso che vale la pena di condividere un mezzo migliore di decapaggio classi parametrizzate rispetto a quello che è il momento soluzione accettata (rendendo globale la classe parametrizzata).

Utilizzando il metodo __reduce__, possiamo fornire un callable che restituirà un'istanza non inizializzata della classe desiderata.

class Base(object): 
    def m(self): 
     return self.__class__.PARAM 

    def __reduce__(self): 
     return (_InitializeParameterized(), (self.PARAM,), self.__dict__) 


def make_parameterized(param_value): 
    class AutoSub(Base): 
     PARAM = param_value 
    return AutoSub 


class _InitializeParameterized(object): 
    """ 
    When called with the param value as the only argument, returns an 
    un-initialized instance of the parameterized class. Subsequent __setstate__ 
    will be called by pickle. 
    """ 
    def __call__(self, param_value): 
     # make a simple object which has no complex __init__ (this one will do) 
     obj = _InitializeParameterized() 
     obj.__class__ = make_parameterized(param_value) 
     return obj 

if __name__ == "__main__": 

    from pickle import dumps, loads 

    a = make_parameterized("a")() 
    b = make_parameterized("b")() 

    print a.PARAM, b.PARAM, type(a) is type(b) 
    a_p = dumps(a) 
    b_p = dumps(b) 

    del a, b 
    a = loads(a_p) 
    b = loads(b_p) 

    print a.PARAM, b.PARAM, type(a) is type(b) 

Vale la pena di leggere il __reduce__ docs un paio di volte per vedere esattamente cosa sta succedendo qui.

Spero che qualcuno lo trovi utile.