[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