[Python] Gestire eventi con callback
Marco Giusti
marco.giusti a posteo.de
Mer 11 Mar 2015 09:02:07 CET
On Tue, Mar 10 2015, Alessandro Re wrote:
> 2015-03-10 17:33 GMT+00:00 Marco Giusti <marco.giusti a posteo.de>:
> > Credo che puoi fare meglio che questo. Vuoi che ogni callback venga
> > invocata una sola volta?
>
> Ah sì, che si possa far meglio è fuori di dubbio :D Comunque no, non
> voglio che *ogni* callback venga invocata una sola volta.
> Ma comunque ammetto che avrei potuto trovare una soluzione molto più
> pulita.
>
> > Inoltre passare un oggetto come primo argomento mi suona tanto da
> > antipattern. Non è sbagliato, ma chiediti che oggetto sia. Mi suona da
> > antipattern per come i metodi e le funzioni in python funzionano.
>
> > come vedi le due chiamate sono identiche ma in più il metodo riceve
> > un oggetto (su cui opera il metodo). Il tuo codice sembra reinventare
> > una ruota che ormai è ben collaudata.
>
> Ok, premetto che mi è chiaro l'esempio che fai, e ci ho pensato un po'
> su queste tue parole, ma non credo di aver capito cosa intendi; ti
> spiace argomentare un pochino?
Ti posto un po' di codice perché credo di essere più chiaro e conciso
con qualche linea di codice che con le parole:
class System(object):
def __init__(self, name):
self.name = name
def ping(self, arg):
return "{}: {}".format(self.name, arg)
class Foo(object):
def __init__(self, system):
self.system = system
def foo(self, arg):
print self.system.ping(arg)
def bar(arg):
print "bar", arg
class Registry(object):
def __init__(self):
self.events = {}
def register(self, event, callback):
self.events.setdefault(event, []).append(callback)
def unregister(self, event, callback):
self.events[event].remove(callback)
def fire_event(self, event, arg):
for callback in self.events[event]:
callback(arg)
registry = Registry()
foo1 = Foo(System("Mercury"))
foo2 = Foo(System("Venus"))
registry.register("ping", foo1.foo)
registry.register("ping", bar)
registry.register("ping", foo2.foo)
registry.register("ping", bar)
registry.fire_event("ping", "hallo")
print "=" * 20
registry.unregister("ping", foo1.foo)
registry.unregister("ping", bar)
registry.fire_event("ping", "hallo")
come vedi funzioni e metodi vengono usati indistintamente ma i metodi, a
differenza delle funzioni, ricevono un argomento in più: self. Tuoi puoi
gestire questo a tua convenienza. Per esempio puoi creare un oggetto
"Foo" con funzione di adapter per un più generico oggetto "System".
L'implementazione della classe "Registry" risulta molto più pulita.
> > Comunque l'ultima parola l'hai tu perché il codice che hai postato non
> > ci aiuta molto a capire esattamente il funzionamento di questi
> > fantomatici oggetti C++.
>
> No, ecco, la questione C++ l'avete ingrandita un po' voi :P Nel senso
> che sì, io ho degli oggetti C++ sottostanti, ma il punto che volevo
> fare è solo che voglio che non esistano oggetti python che facciano
> riferimento ad oggetti C++ ormai distrutti... C++ l'ho nominato solo
> per giustificare il fatto che gli oggetti python, dopo la remove(),
> dovrebbero essere cancellati ovunque. Tutto qua :)
>
> L'unica cosa che ho omesso è che, in remove(), oltre a chiamare le
> callback, io ho anche una chiamata tipo
> `self._oggetto_cpp.distruggi()` e se dopo di essa provo a fare
> `self._oggetto_cpp.metodo()` ottengo un bel errore :)
E' possibile, ma è l'esatta interazione degli oggetti che mi sfugge e
"no code no party".
Quando l'oggetto C++ (C) viene distrutto emette un evento (E) ed è
l'oggetto python (P) che lo distrugge esplicitamente? In casi come
questo trovo molto utile scrivere, io lo faccio a mano con carta e
penna, un diagramma degli stati. In questo specifico caso opterei per
creare uno stato intermedio e gli darei un nome altisonante come
walking_dead, dove l'oggetto è ancora disponibile per gestire gli ultimi
eventi, ma verrà distrutto alla fine di questi.
class Handler:
def __init__(self, cplusplus):
self.__c = cplusplus
self.__cb = {"remove": []}
def remove(self):
self.__c.remove() # emette l'ultimo evento prima di morire
def on_remove(self, *args):
for callback in self.__cb["remove"]:
callback(*args)
self.__cb.clear()
self.__c = None
Questo però implica che l'unico metodo chiamato esternamente sia
"remove".
Che "self._oggetto_cpp.metodo()" lanci un errore se prima è stato
chiamato "self._oggetto_cpp.distruggi()" è cosa buona e giusta e il
problema è a valle, non a monte.
Ciao
m.
Maggiori informazioni sulla lista
Python