2011-10-12 24 views
5

Ho una classe che specifica un insieme di funzioni di callback (qui indicato come cb1 e cb2). Tengo una mappa di questi che voglio chiamare dopo qualche evento.Riferimento Python al callback nel dizionario

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 

Riesci a individuare il problema?

Cosa accade, è che quando la classe viene inizializzato, i valori di cb1 e cb2 (che sono entrambi None) vengono copiati nella mappa. Quindi, anche dopo che un utente "registra" il callback (assegnando a cb1), il valore nella mappa è ancora None e non viene richiamato nulla.

Poiché in Python non esiste qualcosa come "di riferimento", come posso rimediare?

+0

Nitpick: tutto è passato 'per riferimento' in Python. Ma è per riferimento, non per * nome *: se si riassembla il nome in un altro oggetto, questo non aggiorna altri riferimenti a qualunque nome puntasse. –

risposta

9

Perché non fare in modo che la classe gestisca esplicitamente la registrazione?

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

Hai ragione. Il mio dizionario è in realtà più complesso di quello che ho presentato - ha riferimenti a funzioni di analisi e altre cose, quindi inizialmente ho trascurato questo aspetto come soluzione non compatibile. Ma vedendolo ora, è ovviamente il modo migliore per farlo. Grazie! –

1

Aggiungere una funzione di registrazione. In classe Foo:

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

invece di:

f.cb1 = mycallback 

uso:

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

Grazie per questo - l'altro ragazzo ti ha appena battuto. A proposito, il mio OP ha avuto un refuso - ho avuto 'cb1 = mycallback' invece di' f.cb1 = mycallback' quindi potresti voler modificare la tua risposta per riflettere. –

-1

Al contrario, tutto è "riferimento" in Python. Ma stai copiando un riferimento a None nel tuo dizionario e la modifica dello slot originale non fa nulla per quel riferimento. Se si desidera mantenere un livello aggiuntivo di riferimento indiretto, il modo più semplice sarebbe quello di memorizzare le stringhe. Se tutte le tue callback sono attributi di questa classe, elimina map e archivia semplicemente un elenco di nomi di attributi callback. callback_names = ['cb1', 'cb2'], quindi utilizzare getattr(self, callback_name)() per richiamare la richiamata. Se è necessario disporre di una mappa, è possibile eseguire map = {'one': 'cb1', 'two': 'cb2'}.

Si potrebbe anche fare qualcosa di fantasioso con le proprietà, ma ciò sembra inutilmente complicato.

0

Con un descrittore delegato e un po 'di trucco con attributo.

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

Intelligente, ma sospetto che l'interrogante non abbia realmente bisogno di una soluzione così complicata. –

0

Perché avete bisogno di impostare una variabile diversa per personalizzare la richiamata di quello che viene effettivamente utilizzato per eseguirlo? Se si utilizza la stessa variabile, il problema scompare.

Con un po 'di zucchero sintattico che potrebbe assomigliare a questo:

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 
Problemi correlati