| """ |
| 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() |
| |
| |