2011-10-12 52 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