Skip to main content
 首页 » 编程设计

Python单例类

2025年05月04日14lvdongjie

我正在使用 Python 在 os x 10.6 上为 firefox 5.1 和 selenium webdrive v.2 编写测试套件 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 

我无法理解这一点。

请您参考如下方法:

就创建一个类的单个实例而言,你的装饰器对我来说似乎工作正常,所以我没有看到你的问题#1。它并没有像你想象的那样做:每次你使用装饰器时,都会有一个新的 instances 字典,而且里面只有一个项目,所以实际上没有任何理由使用字典在那里——您需要一个可变容器以便您可以修改它,但我会使用一个列表,或者,在 Python 3 中,可能是一个 nonlocal 变量。但是,它确实执行了其预期功能,即确保装饰类只有一个实例。

如果你问为什么在关闭对象后不能创建对象的新实例,那么,你没有编写任何代码来允许在那种情况下创建另一个实例,Python 无法猜测你希望它发生。单例意味着该类永远只有一个实例。您已经创建了那个实例;你不能再创建一个。

至于#2,您的@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 

您可以将所需的行为注入(inject)现有的类对象,但在我看来,这是不必要的复杂,因为它需要(在 Python 2.x 中)创建一个方法包装器,而且您还必须处理这种情况其中被装饰的类自己已经有一个 __new__() 方法。

您可能认为您可以编写一个 __del__() 方法来允许在没有对现有实例的引用时创建一个新的单例。这是行不通的,因为始终存在对实例的类内部引用(例如,Fire._instance),因此永远不会调用 __del__()。一旦你有了一个单例,它就会一直存在。如果您在关闭旧的单例后想要一个新的单例,您可能实际上并不想要单例,而是想要别的东西。 context manager有可能。

在某些情况下可以重新实例化的“单例”对我来说是非常奇怪和意外的行为,我建议不要这样做。尽管如此,如果那是您真正想要的,您可以在 close_() 方法中执行 self.__class__._instance = None。或者您可以编写一个单独的方法来执行此操作。它看起来很丑,这很合适,因为它很丑。 :-)

我认为您的第三个问题也源于这样一个事实,即您希望单例在您调用 close_() 之后以某种方式消失,而您还没有对该行为进行编程。