【发布时间】:2021-02-17 08:49:07
【问题描述】:
我有一个基类(或接口;在下面的Logger)的多个实现,它们可以互换使用。每个实现都以不同的方式处理各种事件,用户可以指定他们想要使用的记录器类型(LoggerA 或LoggerB):
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