Proxying una classe in Python
-
21-12-2019 - |
Domanda
Sto usando il modulo Python-MPD2 per controllare un lettore multimediale su un Raspberry PI in un'applicazione GUI. Quindi, vorrei gestire con grazia errori e timeout di connessione (il giocatore in questione cade le connessioni MPD dopo 60 secondi) in background. Tuttavia, il modulo MPD non ha un singolo punto di ingresso attraverso il quale vengono inviati tutti i comandi o le informazioni che sono riparati a patch.
Vorrei una classe che consente di accedere a tutti gli stessi metodi di mpd.mpdclient, ma io aggiunci la mia gestione degli errori. In altre parole, se lo faccio:
client.play()
.
E un errore di connection è lanciato, vorrei prenderlo e inviare nuovamente lo stesso comando. Oltre al piccolo ritardo causato dal dover ricollegare al server, l'utente non dovrebbe notare che qualsiasi cosa è ammiss.
Finora, ecco la soluzione con cui ho trovato. Sta lavorando nella mia applicazione, ma non soddisfa davvero i miei obiettivi.
from functools import partial
from mpd import MPDClient, ConnectionError
class PersistentMPDClient(object):
def __init__(self, host, port):
self.host = host
self.port = port
self.client = MPDClient()
self.client.connect(self.host, self.port)
def command(self, cmd, *args, **kwargs):
command_callable = partial(self.client.__getattribute__(cmd), *args, **kwargs)
try:
return command_callable()
except ConnectionError:
# Mopidy drops our connection after a while, so reconnect to send the command
self.client._soc = None
self.client.connect(self.host, self.port)
return command_callable()
.
Potrei aggiungere un metodo a questa classe per ogni singolo comando MPD, ad esempio.:
def play(self):
return self.command("play")
.
Ma questo sembra lontano dal modo migliore per realizzarlo.
Soluzione
Se non ti dispiace creare un elenco di tutte le 91 stringhe che formano nomi di comando , puoi fare qualcosa lungo le linee di Questa risposta .Credo che questo approccio abbia molti vantaggi perché coinvolge meno magia.
otoh, 91 è davvero molto.Quindi ecco una soluzione automatica, facendo uso di un __getattr__
personalizzato, che restituisce un wrapper:
from functools import partial
import types
class DummyClient(object):
def connect(self, *a, **kw): print 'connecting %r %r' % (a, kw)
def play(self): print 'playing'
def stop(self): print 'stopping'
class PersistentMPDClient(object):
def __init__(self, host, port):
self.host = host
self.port = port
self.client = DummyClient()
self.client.connect(self.host, self.port)
def __getattr__(self, attr, *args):
cmd = getattr(self.client, attr, *args)
if isinstance(cmd, types.MethodType):
# a method -- wrap
return lambda *a, **kw: self.command(attr, *a, **kw)
else:
# anything else -- return unchanged
return cmd
def command(self, cmd, *args, **kwargs):
command_callable = partial(self.client.__getattribute__(cmd), *args, **kwargs)
try:
return command_callable()
except ConnectionError:
# Mopidy drops our connection after a while, so reconnect to send the command
self.client._soc = None
self.client.connect(self.host, self.port)
return command_callable()
c = PersistentMPDClient(hostname, port)
c.play()
c.stop()
.
Come stavo scrivendo in questo, ho notato @Mattoufoutu aveva pubblicato una soluzione simile (ci sono alcune differenze, però).Non so perché lo ha cancellato ... Se quella risposta viene indelettata, darei volentieri il credito che merita.