5

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?

3 Answers 3

5

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")
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you! The lines notify = make_notify(typ) and notify.__name__ = "notify_%s" % typ should use type (instead of typ), correct?
Oops, self.create(type, ...) should be self.create(typ, ...). Everywhere you wrote type, I suggest use something different, maybe kind instead -- to completely distinguish it from the Python builtin.
Love the class decorator concept!
1

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

Comments

1

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.

1 Comment

I think this fails if the function is called with more than one positional argument or a named argument called 'type'.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.