使用单例模式作为计数器

3 投票
4 回答
2078 浏览
提问于 2025-04-17 19:14

我有一个自动化测试,它使用一个函数来把截图保存到一个文件夹里。这个函数会被多个截图实例调用。在每次测试运行时,都会创建一个新的文件夹,所以我不在乎计数器重置的问题。为了能反映出这些截图的拍摄顺序,我需要想出一些可以按顺序排序的名字。这是我的解决方案:

def make_screenshot_file(file_name):
    order = Counter().count
    test_suites_path = _make_job_directory()
    return make_writable_file(os.path.join(test_suites_path,'screenshot',file_name % order))


class Counter():
    __counter_instance = None

    def __init__(self):
        if Counter.__counter_instance is None:
            self.count = 1
            Counter.__counter_instance = self
        else: 
            Counter.__counter_instance.count += 1
            self.count =  Counter.__counter_instance.count

这个方法对我来说运行得很好。但我一直在想,应该有更简单的方法来解决这个问题。真的有吗?如果单例模式是唯一的解决办法,我的代码有没有什么可以优化的地方?

4 个回答

1

使用静态方法和变量。这种做法虽然不太符合 Python 的风格,但相对简单。

def make_screenshot_file(file_name):
    order = Counter.count() #Note the move of the parens
    test_suites_path = _make_job_directory()
    return make_writable_file(os.path.join(test_suites_path,'screenshot',file_name % order))

class Counter():
  count_n = 0

  @staticmethod
  def count():
    Counter.count_n += 1
    return Counter.count_n


print Counter.count()
print Counter.count()
print Counter.count()
print Counter.count()
print Counter.count()


atarzwell@freeman:~/src$ python so.py
1
2
3
4
5
2

为什么不直接这样做呢

order = time.time()

或者可以试试这样做

import glob #glob is used for unix like path expansion
order = len(glob.glob(os.path.join(test_suites_path,"screenshot","%s*"%filename))
4

你想做的其实是在模拟一个全局变量。

这样做没有什么好理由。如果你真的想要一个全局变量,那就直接定义一个全局变量吧。

你可以创建一个简单的 Counter 类,每次访问时让 count 增加 1,然后创建这个类的全局实例。不过,标准库里已经有类似的功能可以直接使用,像 itertools.count,正如 DSM 在评论中提到的。

所以:

import itertools

_counter = itertools.count()
def make_screenshot_file(file_name):
    order = next(_counter)
    test_suites_path = _make_job_directory()
    return make_writable_file(os.path.join(test_suites_path,'screenshot',file_name % order))

我不太明白你为什么这么担心存储或时间的消耗,因为我想不出有什么程序会在乎你是用 8 字节还是 800 字节来存一个对象,反正你也不可能有多个这样的对象,或者说访问它需要 3 纳秒还是 3 微秒,当你只会访问几次的时候。

不过如果你真的担心,正如你可以从 源代码看到的,count 是用 C 实现的,内存使用非常高效,如果你不做什么复杂的操作,基本上生成每个数字只需要一次 PyNumber_Add,这比解释几行代码要简单得多。


既然你问了,这里有个方法可以通过使用 _count 类属性来大幅简化你现有的代码,而不是使用 __counter_instance 类属性:

class Counter():
    _count = 0
    def count(self):
        Counter._count += 1
        return Counter.count

当然,现在你需要用 Counter().count() 而不是 Counter().count——但如果这很重要,你可以用 @property 来轻松解决这个问题。

值得注意的是,使用经典类而不是新式类(在括号里什么都不传)是个很糟糕的主意,如果你确实想要经典类,应该去掉括号。而且大多数 Python 程序员会把 Counter 这个名字和 collections.Counter 类联系在一起,count 也可以是 @classmethod@staticmethod……到那时,这就和 Andrew T. 的回答完全一致。正如他所指出的,这比你正在做的要简单得多,而且同样符合 Python 的风格。

但实际上,所有这些都不如直接把 _count 定义为模块级的全局变量,并添加一个模块级的 count() 函数来增加并返回它要好。

撰写回答