【问题标题】:Wrapper class for dispatching to multiple similar instances without code repetition用于分派到多个类似实例而不重复代码的包装类
【发布时间】:2021-02-17 08:49:07
【问题描述】:

我有一个基类(或接口;在下面的Logger)的多个实现,它们可以互换使用。每个实现都以不同的方式处理各种事件,用户可以指定他们想要使用的记录器类型(LoggerALoggerB):

class Logger:
    def log_foo(self, a):
        raise NotImplementedError

    def log_bar(self, b, c):
        raise NotImplementedError


class LoggerA(Logger):
    def log_foo(self, a):
        print(a)

    def log_bar(self, b, c):
        pass  # Don't log this event.


class LoggerB(Logger):
    def log_foo(self, a):
        self._send_to_api(a)

    def log_bar(self, b, c):
        self._send_to_api((b, c))

    def _send_to_api(self, data):
        pass

此外,还应该可以选择一次使用多个记录器实例,其界面与单个记录器实例一样。所以会以如下方式使用:

logger: Logger = create_logger_from_config()
logger.log_foo(...)

所以我要为多个记录器创建这个插入式替代品,我发现自己以以下方式重复了很多代码:

class MultiLogger(Logger):
    def __init__(self, loggers: list[Logger]):
        self.loggers = loggers

    def log_foo(self, a):
        for logger in self.loggers:
            logger.log_foo(a)

    def log_bar(self, b, c):
        for logger in self.loggers:
            logger.log_bar(b, c)

此示例仅包含两种方法,但实际上有大量方法重复此模式。我正在尝试提出一个解决方案来避免这种重复,并且与PEP 544 之后的类型检查兼容。什么是实现这一目标的好选择?


我想过通过__getattribute__动态调度,但这似乎很麻烦:

class MultiLogger(Logger):
    def __init__(self, loggers: list[Logger]):
        self.loggers = loggers

    def __getattribute__(self, name):
        obj = super().__getattribute__(name)
        if callable(obj):
            def proxy(*args, **kwargs):
                try:
                    return obj(*args, **kwargs)
                except NotImplementedError:
                    for logger in self.loggers:
                        getattr(logger, name)(*args, **kwargs)
            return proxy
        return obj

我还考虑将Logger 设为Protocol,然后发送至__getattr__。虽然 works for mypy 没有在 PEP 544 中指定,例如 PyCharm 将其标记为类型不匹配:

from typing import Protocol


class Logger(Protocol):
    def log_foo(self, a): ...
    def log_bar(self, b, c): ...


class MultiLogger:
    def __init__(self, loggers: list[Logger]):
        self.loggers = loggers

    def __getattr__(self, name):
        if callable(getattr(Logger, name, None)):
            return self._make_proxy(name)
        raise AttributeError(name)

    def _make_proxy(self, name):
        def proxy(*args, **kwargs):
            for logger in self.loggers:
                getattr(logger, name)(*args, **kwargs)
        return proxy


# PyCharm (rightfully) complains about the following:
#     "Expected type 'Logger', got 'MultiLogger' instead"
# With mypy it works though.
logger: Logger = MultiLogger([LoggerA(), LoggerB()])

【问题讨论】:

    标签: python oop inheritance interface dispatch


    【解决方案1】:

    这不是 DRY 意义上的真正代码重复。只是类似的代码,可能会重复编写。

    MultiLogger 的设计与其他记录器一致。了解LoggerA 或 B 的人会立即了解多记录器。我认为没有理由改变它。

    不过,有一些替代方案的想法
    这只是为每个方法节省了一行(至少是最重复的那一行):

    def execute_all(self, func):
        for logger in self.loggers:
            func(logger)
    
    def log_foo(self, a):
        self.execute_all(lambda l: l.log_foo(a))
    

    一个更有趣的选择是链接记录器。这将使专用的MultiLogger 类变得不必要:

    class Logger:
        def __init__(self, next=None):
            self.next = next
    
    class LoggerA(Logger):
        def log_foo(self, a):
            print(a)
            if self.next:
                self.next.log_foo(a)
    
    # usage e.g.:
    logger = LoggerA(LoggerB())
    

    【讨论】:

      猜你喜欢
      • 2015-09-10
      • 1970-01-01
      • 2019-03-09
      • 1970-01-01
      • 1970-01-01
      • 2019-05-12
      • 1970-01-01
      • 2012-03-11
      • 2013-12-26
      相关资源
      最近更新 更多