2013-03-14 74 views
5

我有下面的代码片断:动态添加类方法对类

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

的想法是动态地创建一个类方法(如notify_fan_mail)对于每个进料的类型。它的工作原理差不多大,唯一的问题是,打印语句总是打印“通知new_event”,不论何种方法我称之为(同为notify_new_mailnotify_review等)。

我意识到这是因为它使用了分配给类型的最后一个值。我的问题是:我如何动态创建方法使用类型的正确值?

另外,如果我在Python文件中有这个确切的代码,那是将方法添加到Feed类的正确方法,还是有更优雅的方法?

回答

5

使用封闭保存的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)) 

顺便提一下,因为它阴影同名的内建不使用type作为变量名。


更好的方法来修改Feed是创建一个类装饰器。这样可以更清楚地知道,您的代码修改了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

谢谢! 'notify = make_notify(typ)'和'notify .__ name__ ='notify_%s'%typ'行应该使用'type'(而不是'typ'),对吗? – kolrie 2013-03-14 22:17:58

+0

糟糕,'self.create(type,...)'应该是'self.create(typ,...)'。在你写'type'的地方,我建议使用不同的东西,也许'kind',以便将它与Python内建完全区分开来。 – unutbu 2013-03-14 22:22:42

+0

喜欢类装饰者的概念! – kolrie 2013-03-14 23:01:24

1

这个错误是由Python中闭包的性质造成的。您的通知功能中的名称type在封闭范围内绑定到type。当你改变type的值时,它会改变所有关闭它的闭包。要解决这个

的一种方法是使用函数工厂:

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

你正在运行到的问题是,你notify功能未封装的价值type,只是它的名字。所以当你的循环进入下一个元组时,旧的会丢失。

您可以通过type默认参数的函数解决这个问题:

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) 

    ... 

请注意,我已经改变了self参数cls,这可能是比较正确的,因为你让一个类方法。

我认为这是在运行时将方法添加到类中的适当方式。我不确定这是否是您需要做的事情,但没有关于您的任务的更多信息(例如,do_create做什么?)我没有看到任何其他明显的改进。

+0

我认为如果使用多个位置参数或称为'type'的命名参数调用该函数,则会失败。 – 2013-03-14 22:11:08