【问题标题】:Dynamically adding class methods to a class为类动态添加类方法
【发布时间】:2013-03-14 21:58:13
【问题描述】:

我有以下sn-p:

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)。它几乎工作得很好,唯一的问题是 print 语句总是打印“通知 new_event”,无论我调用什么方法(对于 notify_new_mailnotify_review 等)。

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

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

【问题讨论】:

    标签: python metaprogramming class-method


    【解决方案1】:

    使用闭包来保存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")
    

    【讨论】:

    • 谢谢! notify = make_notify(typ)notify.__name__ = "notify_%s" % typ 行应该使用 type(而不是 typ),对吗?
    • 糟糕,self.create(type, ...) 应该是 self.create(typ, ...)。在你写 type 的任何地方,我建议使用不同的东西,也许是 kind ——以完全区别于 Python 内置函数。
    • 喜欢类装饰器的概念!
    【解决方案2】:

    该错误是由 Python 中的闭包特性引起的。通知函数中的名称type 绑定到封闭范围内的type。当您更改 type 的值时,所有引用它的闭包都会更改。

    解决这个问题的一种方法是使用函数工厂:

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

    【讨论】:

      【解决方案3】:

      您遇到的问题是您的notify 函数没有封装值type,只是它的名称。所以当你的 for 循环进入下一个元组时,旧的就丢失了。

      您可以通过将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 做了什么?)我没有看到任何其他明显的改进。

      【讨论】:

      • 我认为如果使用多个位置参数或名为 'type' 的命名参数调用函数会失败。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-11
      • 1970-01-01
      • 2013-01-16
      • 2011-11-15
      • 2012-01-23
      • 2013-01-09
      相关资源
      最近更新 更多