【问题标题】:Proxying a class in Python在 Python 中代理一个类
【发布时间】:2014-05-26 20:55:16
【问题描述】:

我正在使用 python-mpd2 模块在 GUI 应用程序中控制 Raspberry Pi 上的媒体播放器。因此,我想在后台优雅地处理连接错误和超时(有问题的播放器在 60 秒后断开 MPD 连接)。但是,MPD 模块没有单一的入口点,通过它可以发送所有命令或检索我可以修补的信息。

我想要一个允许访问所有与 mpd.MPDClient 相同的方法的类,但让我添加我自己的错误处理。换句话说,如果我这样做:

client.play()

并且抛出了一个连接错误,我想抓住它并重新发送相同的命令。除了由于必须重新连接到服务器而导致的小延迟之外,用户不应该注意到有任何问题。

到目前为止,这是我想出的解决方案。它在我的应用程序中运行,但并没有真正实现我的目标。

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()

我可以为每个 MPD 命令添加一个方法到这个类,例如:

def play(self):
    return self.command("play")

但这似乎远非实现它的最佳方式。

【问题讨论】:

  • 要处理多少个命令
  • 有 91 个命令。并非所有这些都是必需的或将在我的应用程序中使用,但其中很大一部分是。
  • 如果您不介意创建一个包含所有 91 个字符串的列表,这些字符串构成 命令名称,您可以按照 this answer 的方式进行操作。我相信这种方法有很多优点,因为它涉及的魔法更少。 OTOH,91 确实很多,所以更神奇的基于__getattr__ 的解决方案可能更合适。

标签: python metaprogramming proxy-classes mpd


【解决方案1】:

如果您不介意创建一个包含所有 91 个字符串的列表,这些字符串构成 命令名称,您可以按照 this answer 的方式进行操作。我相信这种方法有很多优点,因为它涉及的魔法更少。

OTOH,91 确实很多。所以这是一个自动的解决方案,使用自定义的__getattr__,它返回一个包装器:

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()

在我写这篇文章的时候,我注意到@MatToufoutu 发布了一个类似的解决方案(不过有一些不同)。我不知道她/他为什么删除它...如果该答案未被删除,我很乐意给予它应得的荣誉。

【讨论】:

  • 事实上,我删除了我的答案,因为这种方法不允许在调用 command 方法时执行错误处理(OP 想要)。使错误处理成为可能会使事情变得更复杂,但另一个答案即将到来:)
  • 我的错,搞错了(星期六,你知道 ^^),不过,你的回答比我之前发布的更聪明(类型检查和参数处理),我会把它删除;)
  • @MatToufoutu 如你所愿
  • python-mpd2 模块包含我可以导入和使用的命令名称字典,但是您提出的解决方案目前正在发挥作用。非常感谢。
猜你喜欢
  • 2017-09-26
  • 1970-01-01
  • 1970-01-01
  • 2018-02-13
  • 2017-07-25
  • 2017-08-28
  • 2022-01-23
  • 2016-02-17
  • 2012-06-04
相关资源
最近更新 更多