source: fact/tools/PyDimCtrl/singletonmixin.py@ 15787

Last change on this file since 15787 was 14789, checked in by neise, 12 years ago
added singletonmixin.py for cool Singleton classes :-)
File size: 18.5 KB
Line 
1"""
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
8Singleton creation is threadsafe.
9
10USAGE:
11
12Just inherit from Singleton. If you need a constructor, include
13an __init__() method in your class as you usually would. However,
14if your class is S, you instantiate the singleton using S.getInstance()
15instead of S(). Repeated calls to S.getInstance() return the
16originally-created instance.
17
18For example:
19
20class S(Singleton):
21
22 def __init__(self, a, b=1):
23 pass
24
25S1 = S.getInstance(1, b=3)
26
27
28Most of the time, that's all you need to know. However, there are some
29other useful behaviors. Read on for a full description:
30
311) Getting the singleton:
32
33 S.getInstance()
34
35returns the instance of S. If none exists, it is created.
36
372) The usual idiom to construct an instance by calling the class, i.e.
38
39 S()
40
41is disabled for the sake of clarity.
42
43For one thing, the S() syntax means instantiation, but getInstance()
44usually does not cause instantiation. So the S() syntax would
45be misleading.
46
47Because of that, if S() were allowed, a programmer who didn't
48happen to notice the inheritance from Singleton (or who
49wasn't fully aware of what a Singleton pattern
50does) might think he was creating a new instance,
51which could lead to very unexpected behavior.
52
53So, overall, it is felt that it is better to make things clearer
54by requiring the call of a class method that is defined in
55Singleton. An attempt to instantiate via S() will result
56in a SingletonException being raised.
57
583) Use __S.__init__() for instantiation processing,
59since S.getInstance() runs S.__init__(), passing it the args it has received.
60
61If no data needs to be passed in at instantiation time, you don't need S.__init__().
62
634) If S.__init__(.) requires parameters, include them ONLY in the
64first call to S.getInstance(). If subsequent calls have arguments,
65a SingletonException is raised by default.
66
67If you find it more convenient for subsequent calls to be allowed to
68have arguments, but for those argumentsto be ignored, just include
69'ignoreSubsequent = True' in your class definition, i.e.:
70
71 class S(Singleton):
72
73 ignoreSubsequent = True
74
75 def __init__(self, a, b=1):
76 pass
77
785) For testing, it is sometimes convenient for all existing singleton
79instances to be forgotten, so that new instantiations can occur. For that
80reason, a forgetAllSingletons() function is included. Just call
81
82 forgetAllSingletons()
83
84and it is as if no earlier instantiations have occurred.
85
866) As an implementation detail, classes that inherit
87from Singleton may not have their own __new__
88methods. To make sure this requirement is followed,
89an exception is raised if a Singleton subclass includ
90es __new__. This happens at subclass instantiation
91time (by means of the MetaSingleton metaclass.
92
93
94By Gary Robinson, grobinson@flyfi.com. No rights reserved --
95placed in the public domain -- which is only reasonable considering
96how much it owes to other people's code and ideas which are in the
97public domain. The idea of using a metaclass came from
98a comment on Gary's blog (see
99http://www.garyrobinson.net/2004/03/python_singleto.html#comments).
100Other improvements came from comments and email from other
101people who saw it online. (See the blog post and comments
102for further credits.)
103
104Not guaranteed to be fit for any particular purpose. Use at your
105own risk.
106"""
107
108import threading
109
110class SingletonException(Exception):
111 pass
112
113_stSingletons = set()
114_lockForSingletons = threading.RLock()
115_lockForSingletonCreation = threading.RLock() # Ensure only one instance of each Singleton
116 # class is created. This is not bound to the
117 # individual Singleton class since we need to
118 # ensure that there is only one mutex for each
119 # Singleton class, which would require having
120 # a lock when setting up the Singleton class,
121 # which is what this is anyway. So, when any
122 # Singleton is created, we lock this lock and
123 # then we don't need to lock it again for that
124 # class.
125
126def _createSingletonInstance(cls, lstArgs, dctKwArgs):
127 _lockForSingletonCreation.acquire()
128 try:
129 if cls._isInstantiated(): # some other thread got here first
130 return
131
132 instance = cls.__new__(cls)
133 try:
134 instance.__init__(*lstArgs, **dctKwArgs)
135 except TypeError, e:
136 if e.message.find('__init__() takes') != -1:
137 raise SingletonException, 'If the singleton requires __init__ args, supply them on first call to getInstance().'
138 else:
139 raise
140 cls.cInstance = instance
141 _addSingleton(cls)
142 finally:
143 _lockForSingletonCreation.release()
144
145def _addSingleton(cls):
146 _lockForSingletons.acquire()
147 try:
148 assert cls not in _stSingletons
149 _stSingletons.add(cls)
150 finally:
151 _lockForSingletons.release()
152
153def _removeSingleton(cls):
154 _lockForSingletons.acquire()
155 try:
156 if cls in _stSingletons:
157 _stSingletons.remove(cls)
158 finally:
159 _lockForSingletons.release()
160
161def forgetAllSingletons():
162 '''This is useful in tests, since it is hard to know which singletons need to be cleared to make a test work.'''
163 _lockForSingletons.acquire()
164 try:
165 for cls in _stSingletons.copy():
166 cls._forgetClassInstanceReferenceForTesting()
167
168 # Might have created some Singletons in the process of tearing down.
169 # Try one more time - there should be a limit to this.
170 iNumSingletons = len(_stSingletons)
171 if len(_stSingletons) > 0:
172 for cls in _stSingletons.copy():
173 cls._forgetClassInstanceReferenceForTesting()
174 iNumSingletons -= 1
175 assert iNumSingletons == len(_stSingletons), 'Added a singleton while destroying ' + str(cls)
176 assert len(_stSingletons) == 0, _stSingletons
177 finally:
178 _lockForSingletons.release()
179
180class MetaSingleton(type):
181 def __new__(metaclass, strName, tupBases, dct):
182 if dct.has_key('__new__'):
183 raise SingletonException, 'Can not override __new__ in a Singleton'
184 return super(MetaSingleton, metaclass).__new__(metaclass, strName, tupBases, dct)
185
186 def __call__(cls, *lstArgs, **dictArgs):
187 raise SingletonException, 'Singletons may only be instantiated through getInstance()'
188
189class Singleton(object):
190 __metaclass__ = MetaSingleton
191
192 def getInstance(cls, *lstArgs, **dctKwArgs):
193 """
194 Call this to instantiate an instance or retrieve the existing instance.
195 If the singleton requires args to be instantiated, include them the first
196 time you call getInstance.
197 """
198 if cls._isInstantiated():
199 if (lstArgs or dctKwArgs) and not hasattr(cls, 'ignoreSubsequent'):
200 raise SingletonException, 'Singleton already instantiated, but getInstance() called with args.'
201 else:
202 _createSingletonInstance(cls, lstArgs, dctKwArgs)
203
204 return cls.cInstance
205 getInstance = classmethod(getInstance)
206
207 def _isInstantiated(cls):
208 # Don't use hasattr(cls, 'cInstance'), because that screws things up if there is a singleton that
209 # extends another singleton. hasattr looks in the base class if it doesn't find in subclass.
210 return 'cInstance' in cls.__dict__
211 _isInstantiated = classmethod(_isInstantiated)
212
213 # This can be handy for public use also
214 isInstantiated = _isInstantiated
215
216 def _forgetClassInstanceReferenceForTesting(cls):
217 """
218 This is designed for convenience in testing -- sometimes you
219 want to get rid of a singleton during test code to see what
220 happens when you call getInstance() under a new situation.
221
222 To really delete the object, all external references to it
223 also need to be deleted.
224 """
225 try:
226 if hasattr(cls.cInstance, '_prepareToForgetSingleton'):
227 # tell instance to release anything it might be holding onto.
228 cls.cInstance._prepareToForgetSingleton()
229 del cls.cInstance
230 _removeSingleton(cls)
231 except AttributeError:
232 # run up the chain of base classes until we find the one that has the instance
233 # and then delete it there
234 for baseClass in cls.__bases__:
235 if issubclass(baseClass, Singleton):
236 baseClass._forgetClassInstanceReferenceForTesting()
237 _forgetClassInstanceReferenceForTesting = classmethod(_forgetClassInstanceReferenceForTesting)
238
239
240if __name__ == '__main__':
241
242 import unittest
243 import time
244
245 class singletonmixin_Public_TestCase(unittest.TestCase):
246 def testReturnsSameObject(self):
247 """
248 Demonstrates normal use -- just call getInstance and it returns a singleton instance
249 """
250
251 class A(Singleton):
252 def __init__(self):
253 super(A, self).__init__()
254
255 a1 = A.getInstance()
256 a2 = A.getInstance()
257 self.assertEquals(id(a1), id(a2))
258
259 def testInstantiateWithMultiArgConstructor(self):
260 """
261 If the singleton needs args to construct, include them in the first
262 call to get instances.
263 """
264
265 class B(Singleton):
266
267 def __init__(self, arg1, arg2):
268 super(B, self).__init__()
269 self.arg1 = arg1
270 self.arg2 = arg2
271
272 b1 = B.getInstance('arg1 value', 'arg2 value')
273 b2 = B.getInstance()
274 self.assertEquals(b1.arg1, 'arg1 value')
275 self.assertEquals(b1.arg2, 'arg2 value')
276 self.assertEquals(id(b1), id(b2))
277
278 def testInstantiateWithKeywordArg(self):
279
280 class B(Singleton):
281
282 def __init__(self, arg1=5):
283 super(B, self).__init__()
284 self.arg1 = arg1
285
286 b1 = B.getInstance('arg1 value')
287 b2 = B.getInstance()
288 self.assertEquals(b1.arg1, 'arg1 value')
289 self.assertEquals(id(b1), id(b2))
290
291 def testTryToInstantiateWithoutNeededArgs(self):
292
293 class B(Singleton):
294
295 def __init__(self, arg1, arg2):
296 super(B, self).__init__()
297 self.arg1 = arg1
298 self.arg2 = arg2
299
300 self.assertRaises(SingletonException, B.getInstance)
301
302 def testPassTypeErrorIfAllArgsThere(self):
303 """
304 Make sure the test for capturing missing args doesn't interfere with a normal TypeError.
305 """
306 class B(Singleton):
307
308 def __init__(self, arg1, arg2):
309 super(B, self).__init__()
310 self.arg1 = arg1
311 self.arg2 = arg2
312 raise TypeError, 'some type error'
313
314 self.assertRaises(TypeError, B.getInstance, 1, 2)
315
316 def testTryToInstantiateWithoutGetInstance(self):
317 """
318 Demonstrates that singletons can ONLY be instantiated through
319 getInstance, as long as they call Singleton.__init__ during construction.
320
321 If this check is not required, you don't need to call Singleton.__init__().
322 """
323
324 class A(Singleton):
325 def __init__(self):
326 super(A, self).__init__()
327
328 self.assertRaises(SingletonException, A)
329
330 def testDontAllowNew(self):
331
332 def instantiatedAnIllegalClass():
333 class A(Singleton):
334 def __init__(self):
335 super(A, self).__init__()
336
337 def __new__(metaclass, strName, tupBases, dct):
338 return super(MetaSingleton, metaclass).__new__(metaclass, strName, tupBases, dct)
339
340 self.assertRaises(SingletonException, instantiatedAnIllegalClass)
341
342
343 def testDontAllowArgsAfterConstruction(self):
344 class B(Singleton):
345
346 def __init__(self, arg1, arg2):
347 super(B, self).__init__()
348 self.arg1 = arg1
349 self.arg2 = arg2
350
351 B.getInstance('arg1 value', 'arg2 value')
352 self.assertRaises(SingletonException, B, 'arg1 value', 'arg2 value')
353
354 def test_forgetClassInstanceReferenceForTesting(self):
355 class A(Singleton):
356 def __init__(self):
357 super(A, self).__init__()
358 class B(A):
359 def __init__(self):
360 super(B, self).__init__()
361
362 # check that changing the class after forgetting the instance produces
363 # an instance of the new class
364 a = A.getInstance()
365 assert a.__class__.__name__ == 'A'
366 A._forgetClassInstanceReferenceForTesting()
367 b = B.getInstance()
368 assert b.__class__.__name__ == 'B'
369
370 # check that invoking the 'forget' on a subclass still deletes the instance
371 B._forgetClassInstanceReferenceForTesting()
372 a = A.getInstance()
373 B._forgetClassInstanceReferenceForTesting()
374 b = B.getInstance()
375 assert b.__class__.__name__ == 'B'
376
377 def test_forgetAllSingletons(self):
378 # Should work if there are no singletons
379 forgetAllSingletons()
380
381 class A(Singleton):
382 ciInitCount = 0
383 def __init__(self):
384 super(A, self).__init__()
385 A.ciInitCount += 1
386
387 A.getInstance()
388 self.assertEqual(A.ciInitCount, 1)
389
390 A.getInstance()
391 self.assertEqual(A.ciInitCount, 1)
392
393 forgetAllSingletons()
394 A.getInstance()
395 self.assertEqual(A.ciInitCount, 2)
396
397 def test_threadedCreation(self):
398 # Check that only one Singleton is created even if multiple
399 # threads try at the same time. If fails, would see assert in _addSingleton
400 class Test_Singleton(Singleton):
401 def __init__(self):
402 super(Test_Singleton, self).__init__()
403
404 class Test_SingletonThread(threading.Thread):
405 def __init__(self, fTargetTime):
406 super(Test_SingletonThread, self).__init__()
407 self._fTargetTime = fTargetTime
408 self._eException = None
409
410 def run(self):
411 try:
412 fSleepTime = self._fTargetTime - time.time()
413 if fSleepTime > 0:
414 time.sleep(fSleepTime)
415 Test_Singleton.getInstance()
416 except Exception, e:
417 self._eException = e
418
419 fTargetTime = time.time() + 0.1
420 lstThreads = []
421 for _ in xrange(100):
422 t = Test_SingletonThread(fTargetTime)
423 t.start()
424 lstThreads.append(t)
425 eException = None
426 for t in lstThreads:
427 t.join()
428 if t._eException and not eException:
429 eException = t._eException
430 if eException:
431 raise eException
432
433 def testNoInit(self):
434 """
435 Demonstrates use with a class not defining __init__
436 """
437
438 class A(Singleton):
439 pass
440
441 #INTENTIONALLY UNDEFINED:
442 #def __init__(self):
443 # super(A, self).__init__()
444
445 A.getInstance() #Make sure no exception is raised
446
447 def testMultipleGetInstancesWithArgs(self):
448
449 class A(Singleton):
450
451 ignoreSubsequent = True
452
453 def __init__(self, a, b=1):
454 pass
455
456 a1 = A.getInstance(1)
457 a2 = A.getInstance(2) # ignores the second call because of ignoreSubsequent
458
459 class B(Singleton):
460
461 def __init__(self, a, b=1):
462 pass
463
464 b1 = B.getInstance(1)
465 self.assertRaises(SingletonException, B.getInstance, 2) # No ignoreSubsequent included
466
467 class C(Singleton):
468
469 def __init__(self, a=1):
470 pass
471
472 c1 = C.getInstance(a=1)
473 self.assertRaises(SingletonException, C.getInstance, a=2) # No ignoreSubsequent included
474
475 def testInheritance(self):
476 """
477 It's sometimes said that you can't subclass a singleton (see, for instance,
478 http://steve.yegge.googlepages.com/singleton-considered-stupid point e). This
479 test shows that at least rudimentary subclassing works fine for us.
480 """
481
482 class A(Singleton):
483
484 def setX(self, x):
485 self.x = x
486
487 def setZ(self, z):
488 raise NotImplementedError
489
490 class B(A):
491
492 def setX(self, x):
493 self.x = -x
494
495 def setY(self, y):
496 self.y = y
497
498 a = A.getInstance()
499 a.setX(5)
500 b = B.getInstance()
501 b.setX(5)
502 b.setY(50)
503 self.assertEqual((a.x, b.x, b.y), (5, -5, 50))
504 self.assertRaises(AttributeError, eval, 'a.setY', {}, locals())
505 self.assertRaises(NotImplementedError, b.setZ, 500)
506
507 unittest.main()
508
Note: See TracBrowser for help on using the repository browser.