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.
220 lines
8.4 KiB
220 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() |
|
|
|
|
|
|