auto import from //depot/cupcake/@135843
This commit is contained in:
@@ -1,220 +0,0 @@
|
||||
"""
|
||||
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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user