Python单例类

时间:2011-12-19 14:58:08

标签: python firefox singleton selenium-rc

我正在使用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

我无法理解这一点。

2 个答案:

答案 0 :(得分:6)

就创建一个类的单个实例而言,你的装饰器对我来说似乎没问题,所以我没有看到你的问题#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

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

您可能认为可以编写__del__()方法,以便在没有对现有实例的引用时创建新的单例。这不起作用,因为始终存在对实例的类内部引用(例如,Fire._instance),因此永远不会调用__del__()。一旦你有一个单身人士,它就会留下来。如果你在关闭旧单身后想要一个新的单身,你可能实际上并不想要一个单身而是另外一些。可能有context manager

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

我认为你的第三个问题也来自这样一个事实:当你没有编程那个行为时,你预计单身就会以某种方式消失{@ 1}}。

答案 1 :(得分:2)

问题是您使用该单例类作为装饰器。它根本不是装饰器,所以像它一样使用它是没有意义的。

装饰器需要实际返回装饰对象 - 通常是一个函数,但在你的情况下,类。你刚刚返回一个函数。很明显,当您尝试在isinstance中使用它时,Fire不再引用某个类。