Python 单例类

1 投票
2 回答
1758 浏览
提问于 2025-04-17 08:39

我正在为Firefox 5.1和Selenium WebDriver v.2在OS X 10.6上用Python 2.7编写一个测试套件。

一切都运行得很好,除了创建一个单例类,这个类应该确保只有一个Firefox实例:

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class Fire(object):
    def __init__(self):
        self.driver = webdriver.Firefox()
    def getdriver(self):
        return self.driver
    def close_(self):
        self.driver.close()
    def get(self, url):
        self.driver.get(url)
        return self.driver.page_source

f  = Fire()
f.close_()

在这个时候,如果我再次调用 f=Fire(),什么也不会发生。不会创建新的实例。我的问题是,为什么会出现这种情况?我该怎么做才对呢?

我的第二个问题是,如果我输入:

isinstance(f, Fire)

我会收到这个错误:

TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types

这让我觉得很奇怪……根据我的理解,它应该返回 True

最后一个问题:

当我有一个单例类时,我应该能够这样做:

f = Fire()
f2 = Fire()
f2.get('http://www.google.com')
up to here works, but if I say
f.close_()//then
URLError: urlopen error [Errno 61] Connection refused

我对此无法理解。

2 个回答

2

问题在于你把那个单例类当作装饰器来使用。其实它根本就不是一个装饰器,所以这样用就不太合理。

一个装饰器需要真正返回被装饰的对象——通常是一个函数,但在你的情况下,是一个类。你只是返回了一个函数。所以很明显,当你在 isinstance 中使用它时,Fire 就不再指代一个类了。

7

你的装饰器在创建一个类的单一实例方面对我来说似乎没问题,所以我看不出你提到的第一个问题。它的工作方式和你想象的不太一样:每次使用这个装饰器时,都会生成一个新的instances字典,而字典里只有一个项目,所以其实用字典没什么必要——你需要一个可以修改的容器,我建议用列表,或者在Python 3中可以用nonlocal变量。不过,它确实能确保被装饰的类只有一个实例。

如果你在问为什么在关闭对象后不能再创建新的实例,那是因为你没有写任何代码来允许在这种情况下创建另一个实例,而Python也无法猜测你想要这样做。单例模式意味着这个类只有一个实例。你已经创建了那个实例;你不能再创建另一个。

至于第二个问题,你的@singleton装饰器返回的是一个函数,它会实例化(或者返回一个之前创建的实例)这个类。因此,一旦被装饰,Fire就变成了一个函数,而不是类,这就是为什么你的isinstance()不管用的原因。

在我看来,处理单例模式最简单的方法是把逻辑放在一个类里,而不是放在装饰器里,然后从这个类继承。这从继承的角度来看也很合理,因为单例是一种对象。

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = object.__new__(cls, *args, **kwargs)
        return cls._instance

class Fire(Singleton):
     pass

f1 = Fire()
f2 = Fire()

f1 is f2              # True
isinstance(f1, Fire)  # True

如果你仍然想用装饰器来实现,最简单的方法是在装饰器里创建一个中间类并返回它:

def singleton(D):

    class C(D):
        _instance = None
        def __new__(cls, *args, **kwargs):
            if not cls._instance:
                cls._instance = D.__new__(cls, *args, **kwargs)
            return cls._instance

    C.__name__ = D.__name__

    return C

@singleton
class Fire(object):
    pass

你可以把想要的行为注入到现有的类对象中,但在我看来,这样做复杂得多,因为在Python 2.x中需要创建一个方法包装器,而且你还得处理被装饰的类已经有__new__()方法的情况。

你可能会想写一个__del__()方法,以便在没有对现有实例的引用时可以创建新的单例。但这行不通,因为总是会有一个类内部的引用指向这个实例(例如,Fire._instance),所以__del__()永远不会被调用。一旦你有了单例,它就会一直存在。如果你想在关闭旧的单例后再创建一个新的,你可能实际上并不想要单例,而是想要其他东西。一个上下文管理器可能是个选择。

在某些情况下可以重新实例化的“单例”对我来说是非常奇怪和意外的行为,我建议不要这样做。不过,如果这真的是你想要的,你可以在你的close_()方法里写self.__class__._instance = None。或者你可以写一个单独的方法来实现这个。这样看起来很丑,但这正好符合它的本质,因为它确实很丑。:-)

我觉得你的第三个问题也源于你期望单例在你调用close_()后会消失,但你并没有编程实现这种行为。

撰写回答