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

アイデアは、フィードの種類ごとに 1 つのクラス メソッド ( notify_fan_mailなど) を動的に作成することです。ほとんど問題なく動作しますが、唯一の問題は、呼び出すメソッドに関係なく、printステートメントが常に「notifying new_event」を出力することです ( notify_new_mailnotify_reviewなどでも同じです)。

タイプに割り当てられた最後の値を使用しているためだと思います。私の質問は、typeの正しい値を使用するメソッドを動的に作成するにはどうすればよいですか?

また、Python ファイルにこの正確なコードがある場合、Feed クラスにメソッドを追加する正しい方法ですか、それとももっと洗練された方法がありますか?

4

3 に答える 3

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")
于 2013-03-14T22:08:48.343 に答える
1

このバグは、Pythonのクロージャの性質が原因で発生します。type通知関数の名前はtype、囲んでいるスコープにバインドされています。の値を変更typeすると、それを参照するすべてのクロージャで変更されます。

これを解決する1つの方法は、関数ファクトリを使用することです。

def make_notify_function(type):
    def notify(self, **kwargs):
        print "notifying %s" % type
        self.create(type, **kwargs)
    return notify
于 2013-03-14T22:09:03.200 に答える
1

発生している問題は、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するのか)がなければ、他の明らかな改善は見られません。

于 2013-03-14T22:09:05.340 に答える