Python 单例设计模式跟踪应用统计

Python Singleton design pattern tracking application stats

我有一个 Python 应用程序来执行任务的工作流。这些任务中的每一个都可能在其自己的模块中。完成所有任务列表后,应用程序将关闭。在它关闭之前,我想收集每个任务的相关统计信息。

我正在考虑使用单例模式来提供一个地方来存储所有这些数据,以便我可以在最后检索它。每个任务都将导入单例统计跟踪 class,创建一个实例(class 的通用单个实例)并使用它来存储任何数据。

在我的例子中,我想要一个包来存储每个任务的数据。我一直听说单身人士不好。我想获得有关使用单例设计模式或任何其他建议的意见。

单身人士是否 "bad" 似乎是一个品味问题。当然,它们有它们的位置,单例主题的任何变体都应该适合您。

"Borg pattern"("StatelessProxy" 或 "Monostate" 不太流行,"Borg pattern")可能自从 Alex Martelli 聪明的 ActiveState 以来就一直是 Singleton 的流行Python替代品食谱 Singleton? We don't need no stinkin' singleton: the Borg design pattern。它与 Singleton 的不同之处在于允许 class 的多个不同对象共享公共数据。相比之下,单例模式确保只创建一个 class 实例。

关于 Borg vs 单例问题的讨论可以在这个 Whosebug post: Why is the Borg pattern better than the Singleton pattern in Python 中找到。由于缺少 _init_default_register 方法,post 顶部的实现可能令人费解,该方法的目的是创建和初始化公共数据属性,仅一次。作为参考和比较,这里有完整的实现(在 Python 3 中),它们都创建一个数据属性(一个名为 data 的字典):

在 Python 中实现 Singleton 的标准方法是使用 metaclass;

class Singleton(type):
    def __init__(cls, *args, **kwargs):
        cls.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = super().__call__(*args, **kwargs)
        return cls.__instance

你可以通过给它这个元class:

使你的统计跟踪class成为一个单例
class StatsTrackerSingleton(metaclass=Singleton):
    def __init__(self):
        self.data = {}

    # ... methods to update data, summarize it, etc. ...

Borg 模式更易于实现,并且因为它不使用元class,可能更灵活:

class StatsTrackerBorg():
    __shared_data = {'data':{}}
    # RHS guarantees a common dict named `data`

    def __init__(self):
        """Make every instance use StatsTrackerBorg.__shared_data
        for its attribute dict."""
        self.__dict__ = self.__shared_data

    # ... methods to update data, summarize it, etc. ...

对于上面实现的这两种模式,您可以使用通用字典 data,并且您可以使用点运算符简单地获取和设置共享属性。例如,使用 Borg class:

>>> a = StatsTrackerBorg()
>>> b = StatsTrackerBorg()
>>> a is b          # would be True for StatsTrackerSingleton
False
>>> vars(a) is vars(b)
True

>>> vars(a)
{'data': {}}
>>> a.data['running_time'] = 10000
>>> b.bar = 10
>>> vars(a)
{'data': {'running_time': 10000}, 'bar': 10}

>>> b.foo = 'y'
>>> a.foo
'y'

两个模式之间一个值得注意的区别:"Borg'ed" class 的子class 与超级class 共享相同的公共状态,并且仍然可以添加它的实例可以访问更多的共享状态,而单例 class 的每个子 class 都有自己的唯一实例,因此有自己的公共状态,与超级 class 的状态不相交。对于某些预期的应用程序,其中一种行为可能明显比另一种更合适。