The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame] | 1 | """ |
| 2 | A Python Singleton mixin class that makes use of some of the ideas |
| 3 | found at http://c2.com/cgi/wiki?PythonSingleton. Just inherit |
| 4 | from it and you have a singleton. No code is required in |
| 5 | subclasses to create singleton behavior -- inheritance from |
| 6 | Singleton is all that is needed. |
| 7 | |
| 8 | Assume S is a class that inherits from Singleton. Useful behaviors |
| 9 | are: |
| 10 | |
| 11 | 1) Getting the singleton: |
| 12 | |
| 13 | S.getInstance() |
| 14 | |
| 15 | returns the instance of S. If none exists, it is created. |
| 16 | |
| 17 | 2) The usual idiom to construct an instance by calling the class, i.e. |
| 18 | |
| 19 | S() |
| 20 | |
| 21 | is disabled for the sake of clarity. If it were allowed, a programmer |
| 22 | who didn't happen notice the inheritance from Singleton might think he |
| 23 | was creating a new instance. So it is felt that it is better to |
| 24 | make that clearer by requiring the call of a class method that is defined in |
| 25 | Singleton. An attempt to instantiate via S() will restult in an SingletonException |
| 26 | being raised. |
| 27 | |
| 28 | 3) If S.__init__(.) requires parameters, include them in the |
| 29 | first call to S.getInstance(.). If subsequent calls have parameters, |
| 30 | a SingletonException is raised. |
| 31 | |
| 32 | 4) As an implementation detail, classes that inherit |
| 33 | from Singleton may not have their own __new__ |
| 34 | methods. To make sure this requirement is followed, |
| 35 | an exception is raised if a Singleton subclass includ |
| 36 | es __new__. This happens at subclass instantiation |
| 37 | time (by means of the MetaSingleton metaclass. |
| 38 | |
| 39 | By Gary Robinson, grobinson@transpose.com. No rights reserved -- |
| 40 | placed in the public domain -- which is only reasonable considering |
| 41 | how much it owes to other people's version which are in the |
| 42 | public domain. The idea of using a metaclass came from |
| 43 | a comment on Gary's blog (see |
| 44 | http://www.garyrobinson.net/2004/03/python_singleto.html#comments). |
| 45 | Not guaranteed to be fit for any particular purpose. |
| 46 | """ |
| 47 | |
| 48 | class SingletonException(Exception): |
| 49 | pass |
| 50 | |
| 51 | class MetaSingleton(type): |
| 52 | def __new__(metaclass, strName, tupBases, dict): |
| 53 | if '__new__' in dict: |
| 54 | raise SingletonException, 'Can not override __new__ in a Singleton' |
| 55 | return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict) |
| 56 | |
| 57 | def __call__(cls, *lstArgs, **dictArgs): |
| 58 | raise SingletonException, 'Singletons may only be instantiated through getInstance()' |
| 59 | |
| 60 | class Singleton(object): |
| 61 | __metaclass__ = MetaSingleton |
| 62 | |
| 63 | def getInstance(cls, *lstArgs): |
| 64 | """ |
| 65 | Call this to instantiate an instance or retrieve the existing instance. |
| 66 | If the singleton requires args to be instantiated, include them the first |
| 67 | time you call getInstance. |
| 68 | """ |
| 69 | if cls._isInstantiated(): |
| 70 | if len(lstArgs) != 0: |
| 71 | raise SingletonException, 'If no supplied args, singleton must already be instantiated, or __init__ must require no args' |
| 72 | else: |
| 73 | if len(lstArgs) != cls._getConstructionArgCountNotCountingSelf(): |
| 74 | raise SingletonException, 'If the singleton requires __init__ args, supply them on first instantiation' |
| 75 | instance = cls.__new__(cls) |
| 76 | instance.__init__(*lstArgs) |
| 77 | cls.cInstance = instance |
| 78 | return cls.cInstance |
| 79 | getInstance = classmethod(getInstance) |
| 80 | |
| 81 | def _isInstantiated(cls): |
| 82 | return hasattr(cls, 'cInstance') |
| 83 | _isInstantiated = classmethod(_isInstantiated) |
| 84 | |
| 85 | def _getConstructionArgCountNotCountingSelf(cls): |
| 86 | return cls.__init__.im_func.func_code.co_argcount - 1 |
| 87 | _getConstructionArgCountNotCountingSelf = classmethod(_getConstructionArgCountNotCountingSelf) |
| 88 | |
| 89 | def _forgetClassInstanceReferenceForTesting(cls): |
| 90 | """ |
| 91 | This is designed for convenience in testing -- sometimes you |
| 92 | want to get rid of a singleton during test code to see what |
| 93 | happens when you call getInstance() under a new situation. |
| 94 | |
| 95 | To really delete the object, all external references to it |
| 96 | also need to be deleted. |
| 97 | """ |
| 98 | try: |
| 99 | delattr(cls,'cInstance') |
| 100 | except AttributeError: |
| 101 | # run up the chain of base classes until we find the one that has the instance |
| 102 | # and then delete it there |
| 103 | for baseClass in cls.__bases__: |
| 104 | if issubclass(baseClass, Singleton): |
| 105 | baseClass._forgetClassInstanceReferenceForTesting() |
| 106 | _forgetClassInstanceReferenceForTesting = classmethod(_forgetClassInstanceReferenceForTesting) |
| 107 | |
| 108 | |
| 109 | if __name__ == '__main__': |
| 110 | import unittest |
| 111 | |
| 112 | class PublicInterfaceTest(unittest.TestCase): |
| 113 | def testReturnsSameObject(self): |
| 114 | """ |
| 115 | Demonstrates normal use -- just call getInstance and it returns a singleton instance |
| 116 | """ |
| 117 | |
| 118 | class A(Singleton): |
| 119 | def __init__(self): |
| 120 | super(A, self).__init__() |
| 121 | |
| 122 | a1 = A.getInstance() |
| 123 | a2 = A.getInstance() |
| 124 | self.assertEquals(id(a1), id(a2)) |
| 125 | |
| 126 | def testInstantiateWithMultiArgConstructor(self): |
| 127 | """ |
| 128 | If the singleton needs args to construct, include them in the first |
| 129 | call to get instances. |
| 130 | """ |
| 131 | |
| 132 | class B(Singleton): |
| 133 | |
| 134 | def __init__(self, arg1, arg2): |
| 135 | super(B, self).__init__() |
| 136 | self.arg1 = arg1 |
| 137 | self.arg2 = arg2 |
| 138 | |
| 139 | b1 = B.getInstance('arg1 value', 'arg2 value') |
| 140 | b2 = B.getInstance() |
| 141 | self.assertEquals(b1.arg1, 'arg1 value') |
| 142 | self.assertEquals(b1.arg2, 'arg2 value') |
| 143 | self.assertEquals(id(b1), id(b2)) |
| 144 | |
| 145 | |
| 146 | def testTryToInstantiateWithoutNeededArgs(self): |
| 147 | |
| 148 | class B(Singleton): |
| 149 | |
| 150 | def __init__(self, arg1, arg2): |
| 151 | super(B, self).__init__() |
| 152 | self.arg1 = arg1 |
| 153 | self.arg2 = arg2 |
| 154 | |
| 155 | self.assertRaises(SingletonException, B.getInstance) |
| 156 | |
| 157 | def testTryToInstantiateWithoutGetInstance(self): |
| 158 | """ |
| 159 | Demonstrates that singletons can ONLY be instantiated through |
| 160 | getInstance, as long as they call Singleton.__init__ during construction. |
| 161 | |
| 162 | If this check is not required, you don't need to call Singleton.__init__(). |
| 163 | """ |
| 164 | |
| 165 | class A(Singleton): |
| 166 | def __init__(self): |
| 167 | super(A, self).__init__() |
| 168 | |
| 169 | self.assertRaises(SingletonException, A) |
| 170 | |
| 171 | def testDontAllowNew(self): |
| 172 | |
| 173 | def instantiatedAnIllegalClass(): |
| 174 | class A(Singleton): |
| 175 | def __init__(self): |
| 176 | super(A, self).__init__() |
| 177 | |
| 178 | def __new__(metaclass, strName, tupBases, dict): |
| 179 | return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict) |
| 180 | |
| 181 | self.assertRaises(SingletonException, instantiatedAnIllegalClass) |
| 182 | |
| 183 | |
| 184 | def testDontAllowArgsAfterConstruction(self): |
| 185 | class B(Singleton): |
| 186 | |
| 187 | def __init__(self, arg1, arg2): |
| 188 | super(B, self).__init__() |
| 189 | self.arg1 = arg1 |
| 190 | self.arg2 = arg2 |
| 191 | |
| 192 | b1 = B.getInstance('arg1 value', 'arg2 value') |
| 193 | self.assertRaises(SingletonException, B, 'arg1 value', 'arg2 value') |
| 194 | |
| 195 | def test_forgetClassInstanceReferenceForTesting(self): |
| 196 | class A(Singleton): |
| 197 | def __init__(self): |
| 198 | super(A, self).__init__() |
| 199 | class B(A): |
| 200 | def __init__(self): |
| 201 | super(B, self).__init__() |
| 202 | |
| 203 | # check that changing the class after forgetting the instance produces |
| 204 | # an instance of the new class |
| 205 | a = A.getInstance() |
| 206 | assert a.__class__.__name__ == 'A' |
| 207 | A._forgetClassInstanceReferenceForTesting() |
| 208 | b = B.getInstance() |
| 209 | assert b.__class__.__name__ == 'B' |
| 210 | |
| 211 | # check that invoking the 'forget' on a subclass still deletes the instance |
| 212 | B._forgetClassInstanceReferenceForTesting() |
| 213 | a = A.getInstance() |
| 214 | B._forgetClassInstanceReferenceForTesting() |
| 215 | b = B.getInstance() |
| 216 | assert b.__class__.__name__ == 'B' |
| 217 | |
| 218 | unittest.main() |
| 219 | |
| 220 | |