简体   繁体   English

动态地将类方法添加到类中

[英]Dynamically adding class methods to a class

I have the following snippet: 我有以下代码段:

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

The idea is to dynamically create one class method (like notify_fan_mail ) for each feed type. 我们的想法是为每种Feed类型动态创建一个类方法(如notify_fan_mail )。 It works almost great, the only problem is that the print statement always prints "notifying new_event", regardless of the method I call (same for notify_new_mail , notify_review , etc.). 它工作得非常好,唯一的问题是print语句总是打印“通知new_event”,无论我调用哪种方法(对于notify_new_mailnotify_review等都相同)。

I realize it's because it's using the last value assigned to type. 我意识到这是因为它使用了分配给键入的最后一个值。 My question is: how can I dynamically create methods that would use the correct value for type ? 我的问题是:如何动态创建使用正确值类型的方法

Also, if I have this exact code in a Python file, is that the correct way to add methods to the Feed class, or is there a more elegant way? 另外,如果我在Python文件中有这个确切的代码,这是将方法添加到Feed类的正确方法,还是有更优雅的方法?

Use a closure to preserve the value of kind : 使用闭包来保留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))

By the way, don't use type as a variable name since it shadows the builtin of the same name. 顺便说一句,不要使用type作为变量名,因为它会影响同名的内置。


A more elegant way to modify Feed is to create a class decorator. 修改Feed一种更优雅的方法是创建一个类装饰器。 This makes it clearer that you have code modifying the original definition of 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")

The bug is caused by the nature of closures in Python. 该错误是由Python中的闭包性质引起的。 The name type in your notify functions is bound to type in the enclosing scope. 通知函数中的名称type必须在封闭范围中type When you change type 's value it changes for all closures referring to it. 当你改变type的值时,它会改变引用它的所有闭包。

One way to solve this is use a function factory: 解决此问题的一种方法是使用函数工厂:

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

The issue you're running into is that your notify function isn't encapsulating the value type , just it's name. 您遇到的问题是您的notify功能没有封装值type ,只是它的名称。 So when your for loop goes onto the next tuple, the old one is lost. 因此,当你的for循环进入下一个元组时,旧元素会丢失。

You can fix this by making type a default argument to the function: 你可以通过使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)

    ...

Note that I've changed the self argument to cls , which is probably more correct, since you're making it a class method. 请注意,我已将self参数更改为cls ,这可能更正确,因为您将其设为类方法。

I think this is an appropriate way to go to add methods to a class at runtime. 我认为这是在运行时向类添加方法的合适方法。 I'm not sure if that's necessarily something that you need to be doing, but without more information about your task (for instance, what does do_create do?) I don't see any other obvious improvements. 我不确定这是否是你需要做的事情,但没有关于你的任务的更多信息(例如, do_create做了什么?)我没有看到任何其他明显的改进。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM