开发者

Python reference to callback in dictionary

开发者 https://www.devze.com 2023-04-12 10:47 出处:网络
I have a class that sp开发者_开发技巧ecifies a set of callback functions (shown here as cb1 and cb2).I keep a map of these which I want to call after some event.

I have a class that sp开发者_开发技巧ecifies a set of callback functions (shown here as cb1 and cb2). I keep a map of these which I want to call after some event.

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

Can you spot the problem?

What happens, is that when the class is initialized, the values of cb1 and cb2 (which are both None) are copied into the map. So even after a user 'registers' the callback (by assigning to cb1), the value in the map is still None and nothing gets called.

Since there's no such thing as 'by reference' in Python, how do I remedy this?


Why not make your class explicitly handle registration?

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


Why do you need to set a different variable for customizing the callback than the one that is actually used to execute it? If you use the same variable, the problem disapears.

With some syntactic sugar it could look like this:

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


Add a registration function. In Foo class:

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

and instead of:

f.cb1 = mycallback

use:

f.register('one', mycallback)  


With a delegate descriptor and a bit of attribute trickery.

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


To the contrary, everything is "by reference" in Python. But you're copying a reference to None into your dictionary, and changing the original slot doesn't do anything to that reference. If you want to retain an extra level of indirection, then the simplest way would be to store strings. If all of your callbacks are attributes of this class, get rid of map, and just store a list of callback attribute names. callback_names = ['cb1', 'cb2'], and then use getattr(self, callback_name)() to invoke the callback. If you must have a map, then you can do map = {'one': 'cb1', 'two': 'cb2'}.

You could also do something fancy with properties, but that seems needlessly complicated.

0

精彩评论

暂无评论...
验证码 换一张
取 消