Python单例 - 如何在测试环境中去除(__del__)它们?

1 投票
2 回答
4490 浏览
提问于 2025-04-15 15:07

非常感谢你们到目前为止给我的建议。这个论坛让我对使用测试平台有了更深的理解,我对此非常感激。我的问题是,我在玩一个单例模式,通常我不会删除它,但在测试平台中我需要删除它。那么,有人能教我怎么删除这个东西吗?我从一个基本示例开始,然后把它扩展成一个测试平台,以便我能看到发生了什么。现在我不知道怎么把它去掉!

非常感谢!!

import sys
import logging
import unittest

LOGLEVEL = logging.DEBUG

class Singleton:
    """ A python singleton """

    class __impl:
        """ Implementation of the singleton interface """
        def __init__(self):
            self.log = logging.getLogger()
            self.log.debug("Init %s" % self.__class__.__name__)

        def id(self):
            """ Test method, return singleton id """
            return id(self)


    # storage for the instance reference
    __instance = None

    def __init__(self):
        """ Create singleton instance """
        # Check whether we already have an instance
        if Singleton.__instance is None:
            # Create and remember instance
            Singleton.__instance = Singleton.__impl()

        # Store instance reference as the only member in the handle
        self.__dict__['_Singleton__instance'] = Singleton.__instance

    def __getattr__(self, attr):
        """ Delegate access to implementation """
        return getattr(self.__instance, attr)

    def __setattr__(self, attr, value):
        """ Delegate access to implementation """
        return setattr(self.__instance, attr, value)

class A:
    def __init__(self):
        self.log = logging.getLogger()
        self.log.debug("Init %s" % self.__class__.__name__)
        self.lowclass = Singleton()
        self.id = self.lowclass.id()
        self.log.debug("ID: %s" % self.id)

class B:
    def __init__(self):
        self.log = logging.getLogger()
        self.log.debug("Init %s" % self.__class__.__name__)
        self.lowclass = Singleton()
        self.id = self.lowclass.id()
        self.log.debug("ID: %s" % self.id)


class ATests(unittest.TestCase):

    def testOne(self):
        a = A()
        aid = a.id
        b = B()
        bid = b.id
        self.assertEqual(a.id, b.id)

        #
        # How do I destroy this thing??
        #

        del a
        del b

        a1 = A()
        a1id = a1.id
        self.assertNotEqual(a1id, aid)

if __name__ == '__main__':
    # Set's up a basic logger
    logging.basicConfig( format="%(asctime)s %(levelname)-8s %(module)s %(funcName)s %(message)s", 
                         datefmt="%H:%M:%S", stream=sys.stderr )
    log = logging.getLogger("")
    log.setLevel(LOGLEVEL)
    # 
    suite = unittest.TestLoader().loadTestsFromTestCase(ATests)
    sys.exit(unittest.TextTestRunner(verbosity=LOGLEVEL).run(suite))

2 个回答

0

考虑到你会有很多这样的类,我不太会把它们称为单例。你只是把属性推迟到一个单例类上。确保这个类确实是单例的似乎更好。

你这个解决方案的问题在于,你需要实现一个del方法(这没问题),但还需要一个引用计数器,这听起来不是个好主意。:-)

这里有一个问题,里面有几种实现方式:有没有简单优雅的方法来定义单例?

哪种实现适合你,取决于你想要什么样的单例。是每个特定值对应一个对象,但可以是任何值吗?还是一组预定义的单例?或者是一个真正的单例,也就是只有一个对象?

8

作为Borg的作者,我当然支持@mjv的观点。不过,不管是使用Borg(也叫“单态”)还是Highlander(也叫“单例”),你都需要添加一个“清理所有”的方法,以支持你测试套件中的tearDown。给这个方法起个名字,前面加一个下划线,可以告诉软件的其他部分不要去碰它,但测试通常比较特殊,常常需要操作这些本来是内部的属性。

所以,针对你的具体情况,

class Singleton:
   ...
   def _drop(self):
   "Drop the instance (for testing purposes)."
   Singleton.__instance = None
   del self._Singleton__instance

同样,对于Borg来说,一个_drop方法会释放并清空共享的字典,然后用一个全新的字典替代它。

撰写回答