[Python] Gestire eventi con callback
Alessandro Re
ale a ale-re.net
Lun 9 Mar 2015 20:24:56 CET
Alla fine *credo* di aver trovato una soluzione *quasi* soddisfacente.
Alcune scelte fanno cagare, ma per ora può andare.
Condivido, per un ovvio atto di giustizia cosmica.
2015-03-08 15:12 GMT+00:00 Marco Giusti <marco.giusti a posteo.de>:
> 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)
Questa implementazione va sicuramente bene per un meccanismo basilare,
e le ho usate. Bene, Grazie Marco :)
Però poi mi sono accorto che volevo qualcosa di più elaborato ed
automatico. Fondamentalmente, quello che ho fatto è mettere in
register() un sistema che, quando passo un oggetto, io registro in
quell'oggetto una callback che - all'atto di distruzione - rimuove la
callback originale. E questa callback che rimuove, viene schedulata
per essere eseguita una volta sola, e poi rimossa automaticamente.
Inoltre, per permettere di usare funzioni che incapsulassero i vari
metodi, ho fatto in modo che register() prenda anche come parametro un
oggetto da passare come primo argomento alla callback.
In sostanza, il codice è questo:
class Base:
def register(self, event, target_obj, callback):
if target_obj is None:
def _call_once(*args, **kwargs):
callback(*args, **kwargs)
self.unregister(event, None, _call_once)
self._callbacks.setdefault(event, []).append((None, _call_once))
else:
self._callbacks.setdefault(event, []).append((target_obj, callback))
def _remove_cb(unused, obj):
self.unregister(event, target_obj, callback)
target_obj.register('on_remove', None, _remove_cb)
def unregister(self, event, target_obj, callback):
self._callbacks[event].remove((target_obj, callback))
def _run_callbacks(self, event, *args, **kwargs):
for obj, cb in list(it for it in self._callbacks.get(event, [])):
cb(obj, *args, **kwargs)
def remove(self):
self._run_callbacks('on_remove', self)
Scelte di design:
- passare l'oggetto (target_obj) esplicitamente in register()
- schedulare una callback come "fire once" se target_obj è None.
Avrei potuto usare un protocollo diverso, ma questo mi è sembrato un
buon compromesso per ragioni che non sto a spiegare
- lasciare che quando la callback è "fire once", le venga passato
None come primo parametro. Solo per consistenza col resto
- ogni callback registrata avrà una callback automatica di
de-registrazione quando l'oggetto viene distrutto. Probabilmente è uno
spreco di memoria e ci sono soluzioni migliori (tipo un metodo solo
che rimuove tutte le callback associate all'oggetto, la lo lascio come
esercizio al lettore :D).
Punti interessanti:
- in _run_callback costruisco una nuova lista perché può essere che
cb chiami unregister() durante l'esecuzione. Probabilmente non è la
soluzione migliore, comunque.
- remove fa altre cose, ma è anche un esempio di come usare _run_callbacks.
Non ho fatto dei gran test, ma per ora funzionicchia.
Commenti ben accetti - ma considerate che è piuttosto legato al mio
stile e alle necessità di questa applicazione, non l'ho pensato perché
fosse generico.
Ciauz
~Ale
Maggiori informazioni sulla lista
Python