blob: 7935d6d7c4af17937e9167b1e0701ecb1771e8ee [file] [log] [blame]
The Android Open Source Project52d4c302009-03-03 19:29:09 -08001"""
2A Python Singleton mixin class that makes use of some of the ideas
3found at http://c2.com/cgi/wiki?PythonSingleton. Just inherit
4from it and you have a singleton. No code is required in
5subclasses to create singleton behavior -- inheritance from
6Singleton is all that is needed.
7
8Assume S is a class that inherits from Singleton. Useful behaviors
9are:
10
111) Getting the singleton:
12
13 S.getInstance()
14
15returns the instance of S. If none exists, it is created.
16
172) The usual idiom to construct an instance by calling the class, i.e.
18
19 S()
20
21is disabled for the sake of clarity. If it were allowed, a programmer
22who didn't happen notice the inheritance from Singleton might think he
23was creating a new instance. So it is felt that it is better to
24make that clearer by requiring the call of a class method that is defined in
25Singleton. An attempt to instantiate via S() will restult in an SingletonException
26being raised.
27
283) If S.__init__(.) requires parameters, include them in the
29first call to S.getInstance(.). If subsequent calls have parameters,
30a SingletonException is raised.
31
324) As an implementation detail, classes that inherit
33from Singleton may not have their own __new__
34methods. To make sure this requirement is followed,
35an exception is raised if a Singleton subclass includ
36es __new__. This happens at subclass instantiation
37time (by means of the MetaSingleton metaclass.
38
39By Gary Robinson, grobinson@transpose.com. No rights reserved --
40placed in the public domain -- which is only reasonable considering
41how much it owes to other people's version which are in the
42public domain. The idea of using a metaclass came from
43a comment on Gary's blog (see
44http://www.garyrobinson.net/2004/03/python_singleto.html#comments).
45Not guaranteed to be fit for any particular purpose.
46"""
47
48class SingletonException(Exception):
49 pass
50
51class 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
60class 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
109if __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