2011-10-12 5 views
5

Tengo una clase que especifica un conjunto de funciones de devolución de llamada (que se muestran aquí como cb1 y cb2). Guardo un mapa de estos a los que quiero llamar después de algún evento.Referencia de Python a la devolución de llamada en el diccionario

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 

¿Puedes ver el problema?

¿Qué ocurre, es que cuando se inicializa la clase, los valores de cb1 y cb2 (que son a la vez None) se copian en el mapa. Entonces, incluso después de que un usuario 'registra' la devolución de llamada (asignando a cb1), el valor en el mapa sigue siendo None y no se llama a nada.

Dado que en Python no existe el término "por referencia", ¿cómo lo soluciono?

+0

nitpick: todo lo que se pasa 'por referencia' en Python. Pero es por referencia, no por * nombre *: si vuelve a vincular el nombre a otro objeto, eso no actualiza otras referencias a lo que apuntaba el nombre. –

Respuesta

9

¿Por qué no hacer que su clase maneje explícitamente el registro?

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

Tienes razón. Mi diccionario es en realidad más complejo de lo que presenté, tiene referencias a funciones para analizar, y otras cosas, así que inicialmente pasé por alto esto como una solución no compatible. Pero verlo ahora, obviamente es la mejor manera de hacer esto. ¡Gracias! –

1

Agregue una función de registro. En la clase Foo:

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

y en lugar de:

f.cb1 = mycallback 

uso:

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

Gracias por esto, el otro tipo te acaba de vencer. Por cierto, mi OP tenía un error tipográfico - Tenía 'cb1 = mycallback' en lugar de' f.cb1 = mycallback', por lo que es posible que desee editar su respuesta para reflejar. –

-1

Por el contrario, todo es "por referencia" en Python. Pero está copiando una referencia al None en su diccionario, y cambiar la ranura original no le sirve de nada a esa referencia. Si desea mantener un nivel adicional de direccionamiento indirecto, la forma más sencilla sería almacenar cadenas. Si todas sus devoluciones de llamada son atributos de esta clase, deshágase de map, y simplemente almacene una lista de nombres de atributos de devolución de llamada. callback_names = ['cb1', 'cb2'], y luego use getattr(self, callback_name)() para invocar la devolución de llamada. Si debe tener un mapa, puede hacer map = {'one': 'cb1', 'two': 'cb2'}.

También podría hacer algo elegante con las propiedades, pero eso parece innecesariamente complicado.

0

Con un descriptor de delegado y un truco de atributo.

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

Inteligente, pero sospecho que el interrogador no necesita una solución tan complicada. –

0

¿Por qué necesita para establecer una variable diferente para la personalización de la devolución de llamada que la que se utiliza realmente para ejecutarlo? Si usa la misma variable, el problema desaparece.

Con un poco de azúcar sintáctica que podría tener este aspecto:

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 
Cuestiones relacionadas