Proxy de uma classe em Python
-
21-12-2019 - |
Pergunta
Estou usando o módulo python-mpd2 para controlar um media player em um Raspberry Pi em um aplicativo GUI.Portanto, gostaria de lidar normalmente com erros de conexão e tempos limite (o player em questão interrompe as conexões MPD após 60 segundos) em segundo plano.No entanto, o módulo MPD não tem um único ponto de entrada através do qual todos os comandos são enviados ou são recuperadas informações que eu possa corrigir.
Eu gostaria de uma classe que permitisse acesso a todos os mesmos métodos do mpd.MPDClient, mas deixe-me adicionar meu próprio tratamento de erros.Em outras palavras, se eu fizer:
client.play()
E um erro de conexão é gerado, gostaria de capturá-lo e reenviar o mesmo comando.Além do pequeno atraso causado pela necessidade de se reconectar ao servidor, o usuário não deve perceber que algo está errado.
Até agora, aqui está a solução que encontrei.Está funcionando na minha aplicação, mas realmente não atende aos meus objetivos.
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()
Eu poderia adicionar um método a esta classe para cada comando MPD, por exemplo:
def play(self):
return self.command("play")
Mas esta parece estar longe de ser a melhor maneira de conseguir isso.
Solução
Se você não se importa em criar uma lista de todas as 91 strings que formam nomes de comandos, você pode fazer algo como esta resposta.Acredito que esta abordagem tem muitas vantagens porque envolve menos magia.
OTOH, 91 é realmente muito.Então aqui está uma solução automágica, fazendo uso de um custom __getattr__
, que retorna um 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()
Enquanto escrevia isso, percebi que @MatToufoutu havia postado uma solução semelhante (embora existam algumas diferenças).Não sei por que ele apagou...Se essa resposta não for excluída, eu ficaria feliz em dar-lhe o crédito que ela merece.