python中的策略模式

python中的策略模式,python,strategy-pattern,Python,Strategy Pattern,我来自C#背景,为了实现策略模式,我们总是使用一个接口,例如:ilogger。现在据我所知,在诸如Python之类的duck类型语言中,我们可以避免这种基类/契约 我的问题是,这是利用duck类型实现策略模式的最佳方式吗?而且,这种鸭式键入方式是否能让我的代码的下一个用户清楚地知道这是一个“扩展点”?另外,我认为最好使用类型提示来帮助下一个查看代码的人了解策略的类型,但是对于没有基类/契约的duck类型,您使用哪种类型?已经具体化的课程之一 下面是一些代码: class FileSystemLo

我来自C#背景,为了实现策略模式,我们总是使用一个接口,例如:ilogger。现在据我所知,在诸如Python之类的duck类型语言中,我们可以避免这种基类/契约

我的问题是,这是利用duck类型实现策略模式的最佳方式吗?而且,这种鸭式键入方式是否能让我的代码的下一个用户清楚地知道这是一个“扩展点”?另外,我认为最好使用类型提示来帮助下一个查看代码的人了解策略的类型,但是对于没有基类/契约的duck类型,您使用哪种类型?已经具体化的课程之一

下面是一些代码:

class FileSystemLogger():
    def log(self, msg):
        pass

class ElasticSearchLogger():
    def log(self, msg):
        pass

# if i wanted to use type hints, which type should logger be here?
class ComponentThatNeedsLogger():
    def __init__(self, logger):
        self._logger = logger

# should it be this?
class ComponentThatNeedsLogger():
    def __init__(self, logger : FileSystemLogger):
        self._logger = logger
有人能告诉我什么是处理这个问题的最标准/最具吸引力/可读性的方法吗


我不是在寻找“这是两行代码中的答案”的答案。

如果您真的想一直使用类并强制使用基类,请创建一个和它的一些实现:

属性:用于查找目的

from abc import ABC, abstractmethod

class BaseLogger(ABC):
    """ Base class specifying one abstractmethod log - tbd by subclasses."""
    @abstractmethod
    def log(self, message):
        pass

class ConsoleLogger(BaseLogger):
    """ Console logger implementation."""
    def log(self, message):
        print(message)

class FileLogger(BaseLogger):
    """ Appending FileLogger (date based file names) implementation."""
    def __init__(self):
        import datetime 
        self.fn = datetime.datetime.now().strftime("%Y_%m_%d.log")

    def log(self,message):
        with open(self.fn,"a") as f:
            f.write(f"file: {message}\n")

class NotALogger():
    """ Not a logger implementation."""
    pass
然后使用它们:

# import typing # for other type things

class DoIt:
    def __init__(self, logger: BaseLogger):
        # enforce usage of BaseLogger implementation
        if isinstance(logger, BaseLogger):
            self.logger = logger
        else:
            raise ValueError("logger needs to inherit from " + BaseLogger.__name__)

    def log(self, message):
        # use the assigned logger
        self.logger.log(message)

# provide different logger classes
d1 = DoIt(ConsoleLogger())
d2 = DoIt(FileLogger())

for k in range(5):
    d1.log(str(k))
    d2.log(str(k))

with open(d2.logger.fn) as f:
    print(f.read())

try:
    d3 = DoIt( NotALogger())
except Exception as e:
    print(e)
输出:

0
1
2
3
4 
file: 0
file: 1
file: 2
file: 3
file: 4

logger needs to inherit from BaseLogger


作为旁注:python已经具有相当复杂的日志功能。看看这是否是您查询的唯一目的。

在Python中,由于运行时安全性和优雅的终止,通常不需要编译时类型强制的全面策略模式

如果希望自定义现有代码的某些部分,通常的做法是:

  • 替换适当的方法——无论是通过子类化(包括混入)还是仅仅赋值(方法是活动对象的属性,与其他任何方法一样,可以重新赋值;替换甚至可以不是函数,而是具有
    \uuu call\uu
    的对象);
    • 请注意,在Python中,您可以通过将其定义放在函数中动态创建包含代码(包括函数和类)的对象。然后在封闭函数执行时对定义进行评估,您可以使用可访问变量(aka closure)作为特殊参数;或
  • 在某个点接受回调(或者在适当的时候调用其方法的对象,有效地充当一组回调);或
  • 接受一个字符串参数,该参数是某个集合中的常量,然后代码在
    if
    /
    else
    中测试该参数,或在某个注册表中查找该参数(无论是模块的全局参数还是类或对象的局部参数);
    • 从3.4开始就有了
      enum
      ,但对于简单的情况,它被认为有太多的缺点(调试时无法阅读,需要样板文件),因为Python在灵活性与可伸缩性的比例上比C更倾向于灵活性

据我所知,在Python中实现策略模式的最常用方法是传递函数(或可调用函数)。函数是Python中的第一类对象,因此如果消费者需要的只是一个函数,那么您不需要给它更多。当然,如果您愿意,可以对其进行注释。假设您只想记录字符串:

class ComponentThatNeedsLogger:
    def __init__(self, log_func: Callable[[str], None]):
        self._log_func = log_func
这允许您动态创建一个简单的记录器:

ComponentThatNeedsLogger(
    log_func=print
)
但是您也可以利用类的所有功能来创建一个复杂的记录器,并只传递相关的方法

class ComplexLogger:
    def __init__(self, lots_of_dependencies):
        # initialize the logger here

    def log(self, msg: str): None
        # call private methods and the dependencies as you need.

    def _private_method(self, whatever):
        # as many as you need.

ComponentThatNeedsLogger(
    log_func= ComplexLogger(lots_of_dependencies).log
)

可能是一个傻瓜,在这里你需要什么样的元类
ComponentNeedsLogger。只需要一个具有
log
方法的参数。问题是:您是否定义
*Logger
类,因为
ComponentNeedsLogger
使用调用
log
方法的对象,或者,由于您定义了
*Logger
类,因此需要记录器的
组件是否使用带有
log
方法的对象?您可能只需将函数(例如,键入
Callable[str,None]
)传递给需要触发器的
组件即可。并不是所有的东西都必须用类来实现。也许你可以使用一个
BaseLogger
和一个
@abstractmethod
logger方法
def log(…)
来扩展你的具体记录器?看看你是否想走一条非常明确的基于类的路线…@PatrickArtner:如果我使用与C#相同的形式,我会这样做,但问题是duck键入是否提供了一个更优雅的答案。如果我使用C#形式,我会这样写。我想问是否有一种更“Pythonic”的方法,因为它有duck类型。我也知道python日志模块,我只是为了举例而使用它。在目前这里提供的答案中,我想说我最喜欢这个,因为如果你要编写一个中到大型的应用程序,你将需要稳定的/可读的/可伸缩的代码,而且这种方法似乎最符合这些要求。虽然我想在开始编写这个应用程序之前,我会读一本关于python中OOP的书,所以我会更好地理解它。我明白了,但是如果我以后需要logDebug/logError/logInfo怎么办。。。这是只传递函数的缺点。当然,您可以使用LOGTYPE的第二个参数,但随后您可能会得到一组可选参数,用户必须知道在哪种情况下使用它们的组合。如果您希望为不同类型的消息使用多个日志策略,您可以传递多个函数。对于#1,我不知道这如何适用于我可以有一套策略的情况。在我看来,在飞行中更换方法似乎只是自找麻烦。对于#2,回调可能会起作用,但如果以后需要多个方法,那么在一个回调中,您将尝试填充回调的所有可选参数,以说明多个方法。我同意的对象是一个很好的选择,但我仍然不明白这如何适用于我需要类似str的东西的情况