2013-03-14 18 views
5

Ho il seguente frammento:dinamicamente aggiungendo metodi di classe ad una classe

FEED_TYPES = [ 
    ('fan_mail',  'Fan Mail'), 
    ('review',  'Review'), 
    ('tip',   'Tip'), 
    ('fan_user',  'Fan User'), 
    ('fan_song',  'Fan Song'), 
    ('fan_album', 'Fan Album'), 
    ('played_song', 'Played Song'), 
    ('played_album', 'Played Album'), 
    ('played_radio', 'Played Radio'), 
    ('new_event', 'New Event'), 
] 

class Feed: 
    @classmethod 
    def do_create(cls, **kwargs): 
     print kwargs 

    @classmethod 
    def create(cls, type, **kwargs): 
     kwargs['feed_type'] = type 
     cls.do_create(**kwargs) 

for type_tuple in FEED_TYPES: 
    type, name = type_tuple 

    def notify(self, **kwargs): 
     print "notifying %s" % type 
     self.create(type, **kwargs) 

    notify.__name__ = "notify_%s" % type 
    setattr(Feed, notify.__name__, classmethod(notify)) 

Feed.create("FanMail", to_profile="Gerson", from_profile="Felipe") 
Feed.notify_fan_mail(to_profile="Gerson2", from_profile="Felipe2") 

L'idea è di creare dinamicamente un metodo di classe (come notify_fan_mail) per ogni tipo di avanzamento. Funziona quasi alla grande, l'unico problema è che l'istruzione stampa stampa sempre "notifying new_event", indipendentemente dal metodo che chiamo (stesso per notify_new_mail, notify_review, ecc.).

Mi rendo conto che sta utilizzando l'ultimo valore assegnato al tipo. La mia domanda è: come posso creare dinamicamente metodi che utilizzerebbero il valore corretto per tipo?

Inoltre, se ho questo codice esatto in un file Python, è il modo corretto di aggiungere metodi alla classe Feed o esiste un modo più elegante?

risposta

5

Utilizzare una chiusura di preservare il valore di kind:

for type_tuple in FEED_TYPES: 
    kind, name = type_tuple 
    def make_notify(kind): 
     def notify(self, **kwargs): 
      print "notifying %s" % kind 
      self.create(kind, **kwargs) 
     return notify 
    notify = make_notify(kind) 
    notify.__name__ = "notify_%s" % kind 
    setattr(cls, notify.__name__, classmethod(notify)) 

proposito, non utilizzare type come nome variabile poiché nasconde l'incorporato con lo stesso nome.


un modo più elegante per modificare Feed è quello di creare un decoratore di classe. Ciò rende più chiaro che hai il codice che modifica la definizione originale di Feed.

FEED_TYPES = [ 
    ('fan_mail',  'Fan Mail'), 
    ('review',  'Review'), 
    ('tip',   'Tip'), 
    ('fan_user',  'Fan User'), 
    ('fan_song',  'Fan Song'), 
    ('fan_album', 'Fan Album'), 
    ('played_song', 'Played Song'), 
    ('played_album', 'Played Album'), 
    ('played_radio', 'Played Radio'), 
    ('new_event', 'New Event'), 
] 

def add_feed_types(cls): 
    for type_tuple in FEED_TYPES: 
     kind, name = type_tuple 
     def make_notify(kind): 
      def notify(self, **kwargs): 
       print "notifying %s" % kind 
       self.create(kind, **kwargs) 
      return notify 
     notify = make_notify(kind) 
     notify.__name__ = "notify_%s" % kind 
     setattr(cls, notify.__name__, classmethod(notify)) 
    return cls 

@add_feed_types 
class Feed: 
    @classmethod 
    def do_create(cls, **kwargs): 
     print kwargs 

    @classmethod 
    def create(cls, kind, **kwargs): 
     kwargs['feed_type'] = kind 
     cls.do_create(**kwargs) 


Feed.create("FanMail", to_profile="Gerson", from_profile="Felipe") 
Feed.notify_fan_mail(to_profile="Gerson2", from_profile="Felipe2") 
+0

Grazie! Le righe 'notify = make_notify (typ)' e 'notify .__ name__ =" notify_% s "% typ' dovrebbe usare' type' (invece di 'typ'), corretto? – kolrie

+0

Oops, 'self.create (type, ...)' dovrebbe essere 'self.create (typ, ...)'.Ovunque tu abbia scritto 'type', suggerisco di usare qualcosa di diverso, magari' kind', per distinguerlo completamente dal builtin di Python. – unutbu

+0

Ama il concetto di decoratore di classe! – kolrie

1

Il bug è causato dalla natura delle chiusure in Python. Il nome type nelle funzioni di notifica è associato a type nell'ambito di inclusione. Quando si modifica il valore di type, esso cambia per tutte le chiusure che fanno riferimento ad esso.

Un modo per risolvere questo problema è utilizzare una funzione di fabbrica:

def make_notify_function(type): 
    def notify(self, **kwargs): 
     print "notifying %s" % type 
     self.create(type, **kwargs) 
    return notify 
1

Il problema si sta eseguendo in è che la funzione non è notify incapsulando il valore type, basta il suo nome. Quindi, quando il ciclo for passa alla tupla successiva, quella precedente viene persa.

È possibile risolvere questo problema facendo type un argomento di default alla funzione:

for type, name in FEED_TYPES: # no need to unpack the tuple separately 
    def notify(cls, type=type, **kwargs): # type is an argument and default value 
     print "notyfying %s" % type 
     cls.create(type, **kwargs) 

    ... 

Nota che ho cambiato l'argomento self a cls, che è probabilmente più corretto, dal momento che si sta facendo un metodo di classe.

Penso che questo sia un modo appropriato per aggiungere metodi a una classe in fase di esecuzione. Non sono sicuro che sia necessariamente qualcosa che devi fare, ma senza ulteriori informazioni sul tuo compito (ad esempio, cosa fa do_create?) Non vedo altri ovvi miglioramenti.

+0

Penso che questo non funzioni se la funzione viene chiamata con più di un argomento posizionale o un argomento chiamato 'tipo'. –

Problemi correlati