2013-03-14 19 views
5

I mają następujące fragmentu:klasy dynamicznej metody dodawania do klasy

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") 

Idea jest do dynamicznego tworzenia jednej metody klasy (np notify_fan_mail) dla każdego rodzaju posuwu. Działa prawie świetnie, jedynym problemem jest to, że instrukcja drukuje zawsze drukuje "powiadamiając new_event", niezależnie od metody, którą wywołuję (to samo dla notify_new_mail, notify_review, itp.).

Rozumiem, że to dlatego, że używa ostatniej wartości przypisanej do typu. Moje pytanie brzmi: jak mogę dynamicznie tworzyć metody, które używają poprawnej wartości dla typu?

Ponadto, jeśli mam ten dokładny kod w pliku Python, czy jest to właściwy sposób dodawania metod do klasy Feed lub czy istnieje bardziej elegancki sposób?

Odpowiedz

5

Użyj zamknięcie, aby zachować wartość 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)) 

Nawiasem mówiąc, nie używaj type jako nazwy zmiennej, ponieważ cienie polecenie wbudowane o tej samej nazwie.


bardziej elegancki sposób zmodyfikować Feed jest stworzenie dekorator klasy. Dzięki temu jest wyraźniej, że masz kod modyfikujący oryginalną definicję 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

Dziękujemy! Wiersze 'notify = make_notify (typ)' oraz 'notify .__ name__ =" notify_% s "% typ' powinny używać' type' (zamiast 'typ'), poprawne? – kolrie

+0

Ups, 'self.create (type, ...)' powinno być 'self.create (typ, ...)'.Wszędzie, gdzie napisałeś 'type', sugeruję użycie czegoś innego, może" rodzaju "zamiast - całkowicie odróżnić go od wbudowanego w Python. – unutbu

+0

Uwielbiam koncepcję dekoratora klasy! – kolrie

1

Błąd wynika z charakteru zamknięć w Pythonie. Nazwa type w Twoich funkcjach powiadamiania jest powiązana z type w zasięgu obejmującym. Po zmianie wartości type zmienia się dla wszystkich zamknięć odnoszących się do niej.

Jednym ze sposobów rozwiązania tego problemu jest użycie fabryki funkcję:

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

Kwestia używasz do jest to, że funkcja nie jest enkapsulacji notify wartość type, po prostu jego nazwę. Kiedy twoja pętla for przejdzie do następnej krotki, stara stracona.

Można rozwiązać ten problem poprzez type domyślny argument funkcji:

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) 

    ... 

pamiętać, że zmieniliśmy self argument cls, który jest chyba bardziej poprawne, ponieważ robisz to metoda klasy.

Myślę, że jest to odpowiedni sposób na dodanie metod do klasy w środowisku wykonawczym. Nie jestem pewien, czy to jest koniecznie coś, co musisz robić, ale bez dodatkowych informacji o twoim zadaniu (na przykład, co robi do_create?) Nie widzę innych oczywistych ulepszeń.

+0

Myślę, że to się nie powiedzie, jeśli funkcja zostanie wywołana z więcej niż jednym argumentem pozycyjnym lub nazwanym argumentem o nazwie "typ". –