【问题标题】:Using singleton as a counter使用单例作为计数器
【发布时间】:2013-03-15 18:20:28
【问题描述】:

我有一个自动化测试,它使用为文件夹创建屏幕截图的功能。该函数被多个截图实例调用。在每次测试运行时,都会创建一个新文件夹,所以我不关心计数器重置。为了反映这些截图的拍摄顺序,我必须想出可以按顺序排序的名称。这是我的解决方案:

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

它对我来说很好用。但我一直认为应该有一种更简单的方法来解决这个问题。在那儿?如果单例是唯一的方法,我的代码可以以任何方式优化吗?

【问题讨论】:

  • 这实际上根本不是单例。您创建了许多 Counter 实例,它们都指向一个“特殊实例”。这与只有一个实例不同。
  • 另外,如果您对类属性有足够的了解,可以创建一个名为__counter_instance 的类属性,为什么不直接创建一个count 类属性呢?
  • @abarnert,对不起,我不明白你的第二条评论。你能展示一下整个代码的样子吗?

标签: python singleton python-2.6


【解决方案1】:

您在这里尝试做的是模拟一个全局变量。

没有充分的理由这样做。如果您真的想要一个全局变量,请将其显式设为全局变量。

您可以创建一个简单的Counter 类,每次访问它时将count 递增1,然后创建它的全局实例。但是正如 DSM 在评论中解释的那样,标准库已经在itertools.count 中免费为您提供了类似的东西。

所以:

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 个字节可能很重要你永远不会有超过一个 or,或者当你只访问它几次时,它需要 3ns 或 3us 来访问它。

但是如果您担心,正如您从the source 中看到的那样,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... 这正是安德鲁 T. 的回答。正如他所指出的那样,这比您正在做的事情要简单得多,而且或多或少是 Pythonic。

但实际上,所有这一切并没有比将 _count 设为模块级全局并添加一个模块级 count() 函数来递增和返回它更好。

【讨论】:

    【解决方案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))
    

    【讨论】:

    • 给予-1。可能是 OP 不应该这样做,因为它是一个自动化测试。如果这个自动化测试同时从多台机器上运行,或者可能同时从不同线程/进程中的同一台机器上运行,他就有可能得到不一致的结果。
    • 因为 test_suites_path 名称中已经有时间。我不想在路径中有两个时间名称逻辑。
    • @JoranBeasley:同样的论点也适用。如果两个文件名的时间相同,则写入文件中的数据仅属于1个文件,而不属于它们两个:)
    【解决方案3】:

    使用静态方法和变量。不是很pythonic,但更简单。

    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
    

    【讨论】:

    • 您可以使用C = itertools.count(1),然后使用order = next(C)
    • @Andrew T,所以您的解决方案会比我的解决方案运行得更快,对吗?
    • @Prostak:谁在乎它数到 5 的速度?
    • @Prostak:此外,无论“静态类”是什么,您都不需要。只需将 C 设为模块级全局即可。 (我知道你听说过“全局变量很糟糕”,但是用看起来不像全局变量并且不太有效但具有相同缺点的东西来伪造全局变量也好不到哪里去。)
    • @abarnert,只想让它尽可能精简。它使用的内存越少越好。没有内存泄漏。此外,越快越好。
    【解决方案4】:

    好吧,你可以使用这个解决方案,只要确保你从不初始化 order kwarg!

    函数中的可变 Kwargs 类似于类全局变量。并且该值不会在调用之间重置为默认值,正如您一开始可能会想到的那样!

    def make_screenshot_file(file_name , order=[0]):
        order[0] = order[0] + 1
        test_suites_path = _make_job_directory()
        return make_writable_file(os.path.join(test_suites_path,'screenshot',file_name % order[0]))
    

    【讨论】:

    • 你真的试过这个吗?这将每次返回1。该值不是“重置为默认值”,但这只是因为该值从未更改过。 order+=1 不会改变值0,它只是将新值0+1 绑定到本地名称order。下一次,您将再次将0+1 绑定到本地名称order。以此类推。
    • @abarnert,我的错……它只适用于可变对象,修复了代码。感谢您指出我的错误。
    • 为什么要写order.append(order.pop()+1) 而不是order[0] += 1
    • 你要快!!!!对 UnboundLocalError 问题感到困惑,这完全不相关
    • @andsoa,当我尝试您的第一个答案时,它没有用。所以我把你淘汰了。但后来我尝试了你修改过的那个。哇。有用。印象深刻,又学会了一个新把戏。谢谢! +1
    猜你喜欢
    • 2023-03-22
    • 1970-01-01
    • 2021-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-17
    相关资源
    最近更新 更多