You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

221 lines
8.4 KiB

"""
A Python Singleton mixin class that makes use of some of the ideas
found at http://c2.com/cgi/wiki?PythonSingleton. Just inherit
from it and you have a singleton. No code is required in
subclasses to create singleton behavior -- inheritance from
Singleton is all that is needed.
Assume S is a class that inherits from Singleton. Useful behaviors
are:
1) Getting the singleton:
S.getInstance()
returns the instance of S. If none exists, it is created.
2) The usual idiom to construct an instance by calling the class, i.e.
S()
is disabled for the sake of clarity. If it were allowed, a programmer
who didn't happen notice the inheritance from Singleton might think he
was creating a new instance. So it is felt that it is better to
make that clearer by requiring the call of a class method that is defined in
Singleton. An attempt to instantiate via S() will restult in an SingletonException
being raised.
3) If S.__init__(.) requires parameters, include them in the
first call to S.getInstance(.). If subsequent calls have parameters,
a SingletonException is raised.
4) As an implementation detail, classes that inherit
from Singleton may not have their own __new__
methods. To make sure this requirement is followed,
an exception is raised if a Singleton subclass includ
es __new__. This happens at subclass instantiation
time (by means of the MetaSingleton metaclass.
By Gary Robinson, grobinson@transpose.com. No rights reserved --
placed in the public domain -- which is only reasonable considering
how much it owes to other people's version which are in the
public domain. The idea of using a metaclass came from
a comment on Gary's blog (see
http://www.garyrobinson.net/2004/03/python_singleto.html#comments).
Not guaranteed to be fit for any particular purpose.
"""
class SingletonException(Exception):
pass
class MetaSingleton(type):
def __new__(metaclass, strName, tupBases, dict):
if '__new__' in dict:
raise SingletonException, 'Can not override __new__ in a Singleton'
return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict)
def __call__(cls, *lstArgs, **dictArgs):
raise SingletonException, 'Singletons may only be instantiated through getInstance()'
class Singleton(object):
__metaclass__ = MetaSingleton
def getInstance(cls, *lstArgs):
"""
Call this to instantiate an instance or retrieve the existing instance.
If the singleton requires args to be instantiated, include them the first
time you call getInstance.
"""
if cls._isInstantiated():
if len(lstArgs) != 0:
raise SingletonException, 'If no supplied args, singleton must already be instantiated, or __init__ must require no args'
else:
if len(lstArgs) != cls._getConstructionArgCountNotCountingSelf():
raise SingletonException, 'If the singleton requires __init__ args, supply them on first instantiation'
instance = cls.__new__(cls)
instance.__init__(*lstArgs)
cls.cInstance = instance
return cls.cInstance
getInstance = classmethod(getInstance)
def _isInstantiated(cls):
return hasattr(cls, 'cInstance')
_isInstantiated = classmethod(_isInstantiated)
def _getConstructionArgCountNotCountingSelf(cls):
return cls.__init__.im_func.func_code.co_argcount - 1
_getConstructionArgCountNotCountingSelf = classmethod(_getConstructionArgCountNotCountingSelf)
def _forgetClassInstanceReferenceForTesting(cls):
"""
This is designed for convenience in testing -- sometimes you
want to get rid of a singleton during test code to see what
happens when you call getInstance() under a new situation.
To really delete the object, all external references to it
also need to be deleted.
"""
try:
delattr(cls,'cInstance')
except AttributeError:
# run up the chain of base classes until we find the one that has the instance
# and then delete it there
for baseClass in cls.__bases__:
if issubclass(baseClass, Singleton):
baseClass._forgetClassInstanceReferenceForTesting()
_forgetClassInstanceReferenceForTesting = classmethod(_forgetClassInstanceReferenceForTesting)
if __name__ == '__main__':
import unittest
class PublicInterfaceTest(unittest.TestCase):
def testReturnsSameObject(self):
"""
Demonstrates normal use -- just call getInstance and it returns a singleton instance
"""
class A(Singleton):
def __init__(self):
super(A, self).__init__()
a1 = A.getInstance()
a2 = A.getInstance()
self.assertEquals(id(a1), id(a2))
def testInstantiateWithMultiArgConstructor(self):
"""
If the singleton needs args to construct, include them in the first
call to get instances.
"""
class B(Singleton):
def __init__(self, arg1, arg2):
super(B, self).__init__()
self.arg1 = arg1
self.arg2 = arg2
b1 = B.getInstance('arg1 value', 'arg2 value')
b2 = B.getInstance()
self.assertEquals(b1.arg1, 'arg1 value')
self.assertEquals(b1.arg2, 'arg2 value')
self.assertEquals(id(b1), id(b2))
def testTryToInstantiateWithoutNeededArgs(self):
class B(Singleton):
def __init__(self, arg1, arg2):
super(B, self).__init__()
self.arg1 = arg1
self.arg2 = arg2
self.assertRaises(SingletonException, B.getInstance)
def testTryToInstantiateWithoutGetInstance(self):
"""
Demonstrates that singletons can ONLY be instantiated through
getInstance, as long as they call Singleton.__init__ during construction.
If this check is not required, you don't need to call Singleton.__init__().
"""
class A(Singleton):
def __init__(self):
super(A, self).__init__()
self.assertRaises(SingletonException, A)
def testDontAllowNew(self):
def instantiatedAnIllegalClass():
class A(Singleton):
def __init__(self):
super(A, self).__init__()
def __new__(metaclass, strName, tupBases, dict):
return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict)
self.assertRaises(SingletonException, instantiatedAnIllegalClass)
def testDontAllowArgsAfterConstruction(self):
class B(Singleton):
def __init__(self, arg1, arg2):
super(B, self).__init__()
self.arg1 = arg1
self.arg2 = arg2
b1 = B.getInstance('arg1 value', 'arg2 value')
self.assertRaises(SingletonException, B, 'arg1 value', 'arg2 value')
def test_forgetClassInstanceReferenceForTesting(self):
class A(Singleton):
def __init__(self):
super(A, self).__init__()
class B(A):
def __init__(self):
super(B, self).__init__()
# check that changing the class after forgetting the instance produces
# an instance of the new class
a = A.getInstance()
assert a.__class__.__name__ == 'A'
A._forgetClassInstanceReferenceForTesting()
b = B.getInstance()
assert b.__class__.__name__ == 'B'
# check that invoking the 'forget' on a subclass still deletes the instance
B._forgetClassInstanceReferenceForTesting()
a = A.getInstance()
B._forgetClassInstanceReferenceForTesting()
b = B.getInstance()
assert b.__class__.__name__ == 'B'
unittest.main()