Dynamically adding class methods to a class

非 Y 不嫁゛ 提交于 2020-01-02 04:45:05

问题


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

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?


回答1:


Use a closure to preserve the value of 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.


A more elegant way to modify Feed is to create a class decorator. This makes it clearer that you have code modifying the original definition of 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")



回答2:


The bug is caused by the nature of closures in Python. The name type in your notify functions is bound to type in the enclosing scope. When you change type's value it changes for all closures referring to it.

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



回答3:


The issue you're running into is that your notify function isn't encapsulating the value type, just it's name. So when your for loop goes onto the next tuple, the old one is lost.

You can fix this by making type a default argument to the function:

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.

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.



来源:https://stackoverflow.com/questions/15420989/dynamically-adding-class-methods-to-a-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!