Python 单例类
我正在为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 个回答
问题在于你把那个单例类当作装饰器来使用。其实它根本就不是一个装饰器,所以这样用就不太合理。
一个装饰器需要真正返回被装饰的对象——通常是一个函数,但在你的情况下,是一个类。你只是返回了一个函数。所以很明显,当你在 isinstance
中使用它时,Fire
就不再指代一个类了。
你的装饰器在创建一个类的单一实例方面对我来说似乎没问题,所以我看不出你提到的第一个问题。它的工作方式和你想象的不太一样:每次使用这个装饰器时,都会生成一个新的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_()
后会消失,但你并没有编程实现这种行为。