2011-10-12 51 views
5

我有一個類指定了一組回調函數(這裏顯示爲cb1cb2)。我保留了一些我想在某些事件後致電的地圖。在字典中回調的Python引用

class Foo: 
    cb1 = None 
    cb2 = None 

    def test(self, input): 
     for (name, callback) in map: 
      if name == input: 
       if callback: callback() 
       ... 

    map = {'one':cb1, 'two':cb2} 

def mycallback(): 
    print "mycallback()" 

f = Foo() 
f.cb1 = mycallback # Register our callback 
f.test('one')  # Nothing happens 

你能發現問題嗎?

會發生什麼情況,是當類被初始化時,cb1cb2(這兩者都是None)被複制到地圖。因此,即使用戶「註冊」了回撥(通過分配給cb1),地圖中的值仍然是None,並且不會調用任何內容。

由於在Python中沒有像'引用'那樣的東西,我該如何解決這個問題?

+0

雞蛋裏挑骨頭:一切「引用」在Python通過。但它是通過引用,而不是* name *:如果您將該名稱重新綁定到另一個對象,則不會更新其他引用以指向任何名稱。 –

回答

9

爲什麼不讓你的類明確地處理註冊?

import collections 

class Foo(object): 
    handlers = None 

    def __init__(self): 
     self.handlers = collections.defaultdict(set) 

    def register(self, event, callback): 
     self.handlers[event].add(callback) 

    def fire(self, event, **kwargs): 
     for handler in self.handlers.get(event, []): 
      handler(**kwargs) 

foo = Foo() 
foo.register('one', mycallback) 
foo.fire('one') 
+0

你說得對。我的字典實際上比我提供的更復雜 - 它提供瞭解析和其他功能的參考,所以我最初忽略了這不是一個兼容的解決方案。但現在看到它,這顯然是做到這一點的最佳方式。謝謝! –

1

添加註冊功能。在Foo類:

def register(self, name, cb): self.map[name] = cb 

,而不是和:

f.cb1 = mycallback 

使用:

f.register('one', mycallback) 
+0

謝謝你 - 其他人剛剛擊敗你。順便說一句,我的OP有一個錯字 - 我有'cb1 = mycallback'而不是'f.cb1 = mycallback',所以你可能想編輯你的答案來反映。 –

-1

相反,一切都是 「通過引用」 Python編寫的。但是您將None的引用複製到您的字典中,並且更改原始插槽對該引用沒有任何作用。如果你想保留一個額外的間接級別,那麼最簡單的方法就是存儲字符串。如果您的所有回調都是此類的屬性,請刪除map,並只存儲回調屬性名稱的列表。 callback_names = ['cb1', 'cb2'],然後使用getattr(self, callback_name)()來調用回調。如果你必須有地圖,那麼你可以做map = {'one': 'cb1', 'two': 'cb2'}

你也可以做一些特性的東西,但似乎不必要的複雜。

0

使用委託描述符和一些屬性欺騙。

class Delegate(object): 
    def __get__(self, instance, owner): 
    return instance._cbs.get(self, lambda x: None) 

    def __set__(self, instance, value): 
    if not hasattr(instance, '_cbs'): 
     instance._cbs = {} 
    instance._cbs[self] = value 

    def __delete__(self, instance): 
    if not hasattr(instance, '_cbs'): 
     instance._cbs = {} 
    instance._cbs[self] = lambda x: None 

    def __hash__(self): 
    return id(self) 

class C(object): 
    cb1 = Delegate() 
    map = {'one': 'cb1'} 

    def test(self, cb): 
    getattr(self, self.map[cb])() 

def foo(): 
    print 'bar!' 

c = C() 
c.cb1 = foo 
c.test('one') 
+0

聰明,但我懷疑提問者實際上並不需要這個複雜的解決方案。 –

0

爲什麼您需要爲自定義回調設置一個不同的變量,而不是實際用於執行回調的變量?如果您使用相同的變量,則問題消失。

隨着一些語法糖也可能是這樣的:

class CallbackMap(object): 
    pass 

class Foo(object): 
    callbacks = CallbackMap() 

    def test(self, input): 
     callback = getattr(Foo.callbacks, input) 
     if callback: callback() 

# setup defaults 
Foo.callbacks.one = None 
Foo.callbacks.two = some_default_callback 

# customize 
def mycallback(): 
    print "mycallback()" 

f = Foo() 
Foo.callbacks.one = mycallback # Register our callback 
f.test('one') # works