[Python] Gestire eventi con callback
Marco Giusti
marco.giusti a posteo.de
Dom 8 Mar 2015 16:12:02 CET
On Sun, Mar 08 2015, Alessandro Re wrote:
> Ciao Marco,
>
> intanto grazie per la risposta :)
Non c'è di che :)
> 2015-03-08 7:54 GMT+00:00 Marco Giusti <marco.giusti a posteo.de>:
> > Come registri le callback? Gli oggetti in python sono referenziati da
> > del codice python o solo dalle callback di c++? In questo caso basta
> > che, nel momento in cui distruggi l'oggetto C, deregistri le callback
> > e riduci il numero di referenze del metodo. Se i metodi di un oggetto
> > P sono usati come callback di un solo oggetto C, al momento che
> > tu rilasci tutte le callback non esistono più riferimenti all'oggetto
> > python.
>
> Il problema è proprio la registrazione delle callback... Per ora ho un
> meccanismo piuttosto primitivo:
> quando un evento accade, viene chiamato un metodo on_evento(*args) di python.
> L'idea è che l'oggetto tiene una lista di callback da
> chiamare per quell'evento, e quel metodo è definito più o meno come
>
> def on_evento(self, *args):
> for callback in self._callbacks:
> callback(*args)
>
> Però è per l'appunto primitivo, e non viene assolutamente gestita la
> rimozione delle callback. Prima di avventurarmi ed inventare qualcosa
> che cerchi di automatizzare la rimozione, volevo capire se c'era già
> qualche pattern usato in questi casi.
Il patter che più gli assomigli è l'Observer ma non sono sicuro che la
terminologia sia quella giusta in questo caso. Comunque
l'implementazione è all'incirca quella:
...
self._events = {}
def register(self, event, callback):
self._events.setdefault(event, []).append(callback)
def unregister(self, event, callback):
callbacks = self._events[event]
callbacks.pop(callbacks.index(callback))
def on_event(self, event, *args):
for callback in self._events[event]:
callback(*args)
Così come registri una callback, così la rimuovi. Se non riesci a
definire formalmente un ciclo di vita per i tuoi oggetti, allora è
probabile che nel tuo design qualcosa sia sbagliato.
Ancora una volta ti offro una soluzione semplicistica. Se l'oggetto
prima di essere distrutto scatena l'evento "on_remove":
def on_remove(self, *args):
self.on_event("remove", *args)
del self._events.clear()
def use(...):
obj = Object(...)
event_handler.register("foo", obj.foo)
event_handler.register("remove", obj.remove)
event_handler.register("foo", Object(...).foo)
in questa maniera le uniche referenze ai due oggetti "Object" sono
nell'event_handler. Prima che l'oggetto C++ venga distrutto scatena
l'evento "on_remove" e le referenze dei due oggetti "Object" vengono
riportate a zero.
Fossi in te inizierei con una implementazione di questo tipo. Se è vero
che è primitiva, le specifiche non sembrano ancora definite e trovo
inutile andare a complicarsi troppo la vita. Scrivi un paio di test,
vedi se il codice che viene fuori è soddisfacente o se richiede qualche
hack non troppo elegante e parti da lì per migliorarlo.
> >> Come posso rendere sincrona (bloccante) l'invocazione di un comando?
> >
> > La prima idea che mi viene in mente è di infilare la callback dentro la
> > send e di chiamare sleep per salvare qualche ciclo macchina:
> >
> > def send_data_blocking(data):
> > def go():
> > free_to_go = True
> > free_to_go = False
> > send_data(data, callback=go)
> > while not free_to_go:
> > sleep(0.1)
>
> Grazie, proverò con la sleep :) (btw, in go() non ci andrebbe un
> "nonlocal free_to_go"?)
Per un momento ho creduto che "go" fosse il linguaggio. Non conosco
nonlocal ma hai ragione se dici che c'è un errore grossolano. Più o meno
è questo che avevo in mente, ma non avendo neanche provato il
codice...
def send_data_blocking(data):
def go():
free_to_go[0] = True
free_to_go = [False]
send_data(data, callback=go)
while not free_to_go[0]:
sleep(0.1)
Ciao
m.
Maggiori informazioni sulla lista
Python