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.
精彩评论