Last active
November 29, 2019 12:40
-
-
Save wellic/72a8702e42c4b345f9149ef679bf5dce to your computer and use it in GitHub Desktop.
Example python 3.6+ Singleton
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from weakref import WeakValueDictionary | |
METHOD_GET_INSTANCE = 'get_instance' | |
METHOD_REMOVE_INSTANCE = 'remove_instance' | |
METHOD_CLEAR_INSTANCE = 'clear_instance' | |
class MetaSingleton(type): | |
""" | |
Singleton metaclass | |
Based on Python Cookbook 3rd Edition Recipe 9.13 | |
Only one instance of a class can exist. | |
Does not work with __slots__ | |
Usage: | |
class Spam(metaclass=MetaSingleton): | |
def __init__(self, *args, **kwargs): | |
print('Creating Data') | |
self.data = 'some data' | |
@staticmethod | |
def clear_instance(): | |
''' | |
This method usages for tests and called from Spam.remove_instance() | |
''' | |
o = Spam.get_instance() | |
if o: | |
o.data = None | |
> a = Spam() | |
Creating Data | |
> b = Spam() | |
> a is b | |
True | |
> c = Spam() | |
> a is c | |
True | |
""" | |
_instances = {} | |
def __new__(cls, clsname, bases, dct): | |
newclass = super(MetaSingleton, cls).__new__(cls, clsname, bases, dct) | |
def get_instance(): | |
if newclass in MetaSingleton._instances: | |
return MetaSingleton._instances[newclass] | |
return None | |
def _clear_instance(): | |
pass | |
def remove_instance(): | |
if newclass in MetaSingleton._instances: | |
if hasattr(newclass, METHOD_CLEAR_INSTANCE): | |
MetaSingleton._instances[newclass].clear_instance() | |
MetaSingleton._instances[newclass] = None | |
del MetaSingleton._instances[newclass] | |
setattr(newclass, METHOD_GET_INSTANCE, get_instance) | |
setattr(newclass, METHOD_REMOVE_INSTANCE, remove_instance) | |
if not hasattr(newclass, METHOD_CLEAR_INSTANCE): | |
setattr(newclass, METHOD_CLEAR_INSTANCE, staticmethod(_clear_instance)) | |
return newclass | |
def __call__(cls, *args, **kwargs): | |
if cls in cls._instances and cls._instances[cls]: | |
return cls._instances[cls] | |
cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs) | |
return cls._instances[cls] | |
@staticmethod | |
def remove_instance(): | |
pass | |
@staticmethod | |
def get_instance(): | |
pass | |
class MetaCachedSingleton(type): | |
""" | |
Caching metaclass | |
Based on Python Cookbook 3rd Edition Recipe 9.13 and 8.25 | |
Child classes will only create new instances of themselves if one doesn't already exist. | |
Does not work with __slots__ | |
Usage: | |
class Spam(metaclass=MetaCachedSingleton): | |
def __init__(self, name): | |
print(f'Creating Spam({name})') | |
self.name = name | |
@staticmethod | |
def clear_instance(name): | |
''' | |
This method usages for tests and called from Spam.remove_instance(name) | |
''' | |
print(f'Removing Spam({name})') | |
self.name = None | |
> a = Spam('Guido') | |
Creating Spam('Guido') | |
> b = Spam('Diana') | |
Creating Spam('Diana') | |
> c = Spam('Guido') # Cached args | |
> a is b | |
False | |
> a is c | |
True | |
> Spam.remove_instance('Guido') | |
""" | |
_cache = WeakValueDictionary() | |
def __call__(self, *args, **kwargs): | |
if args in self._cache: | |
return self._cache[args] | |
else: | |
obj = super().__call__(*args, **kwargs) | |
self._cache[args] = obj | |
return obj | |
def __new__(cls, clsname, bases, dct): | |
newclass = super(MetaCachedSingleton, cls).__new__(cls, clsname, bases, dct) | |
def get_instance(*args): | |
if args in MetaCachedSingleton._cache: | |
return MetaCachedSingleton._cache[args] | |
return None | |
def _clear_instance(*args): | |
pass | |
def remove_instance(*args): | |
if args in MetaCachedSingleton._cache: | |
if hasattr(newclass, METHOD_CLEAR_INSTANCE): | |
MetaCachedSingleton._cache[args].clear_instance(*args) | |
del MetaCachedSingleton._cache[args] | |
setattr(newclass, METHOD_GET_INSTANCE, get_instance) | |
setattr(newclass, METHOD_REMOVE_INSTANCE, remove_instance) | |
if not hasattr(newclass, METHOD_CLEAR_INSTANCE): | |
setattr(newclass, METHOD_CLEAR_INSTANCE, staticmethod(_clear_instance)) | |
return newclass | |
@staticmethod | |
def remove_instance(*args): | |
pass | |
@staticmethod | |
def get_instance(*args): | |
pass |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from unittest import TestCase | |
from commons.consul.singleton import MetaSingleton, MetaCachedSingleton | |
TEST_DATA1 = 'Created 1' | |
TEST_DATA2 = 'Created 2' | |
TEST_ARG1 = 'ARG1' | |
TEST_ARG2 = 'ARG2' | |
class SingletonWithoutCleanInstance(metaclass=MetaSingleton): | |
def __init__(self, *args, **kwargs): | |
self.data = TEST_DATA1 | |
class SingletonWithCleanInstance(metaclass=MetaSingleton): | |
def __init__(self, *args, **kwargs): | |
self.data = TEST_DATA1 | |
@staticmethod | |
def clear_instance(): | |
o = SingletonWithCleanInstance.get_instance() | |
if o: | |
o.data = None | |
class CachedSingletonWithoutCleanInstance(metaclass=MetaCachedSingleton): | |
def __init__(self, *args, **kwargs): | |
self.data = TEST_DATA1 | |
class CachedSingletonWithCleanInstance(metaclass=MetaCachedSingleton): | |
def __init__(self, *args, **kwargs): | |
self.data = TEST_DATA1 | |
@staticmethod | |
def clear_instance(*args): | |
o = CachedSingletonWithCleanInstance.get_instance(*args) | |
if o: | |
o.data = None | |
class SingletonTestCase(TestCase): | |
def test_SingletonWithoutCleanInstance(self): | |
a = SingletonWithoutCleanInstance.get_instance() | |
self.assertIsNone(a) | |
a = SingletonWithoutCleanInstance() | |
self.assertIsInstance(a, SingletonWithoutCleanInstance) | |
self.assertIs(a, SingletonWithoutCleanInstance.get_instance()) | |
self.assertEqual(a.data, TEST_DATA1) | |
b = SingletonWithoutCleanInstance() | |
self.assertIs(a, b) | |
self.assertEqual(a.data, TEST_DATA1) | |
self.assertEqual(b.data, TEST_DATA1) | |
b.data = TEST_DATA2 | |
self.assertEqual(a.data, TEST_DATA2) | |
self.assertEqual(b.data, TEST_DATA2) | |
#clear | |
SingletonWithoutCleanInstance.remove_instance() | |
self.assertIsNone(SingletonWithoutCleanInstance.get_instance()) | |
self.assertEqual(a.data, TEST_DATA2) | |
self.assertEqual(b.data, TEST_DATA2) | |
def test_SingletonWithCleanInstance(self): | |
o = SingletonWithCleanInstance.get_instance() | |
self.assertIsNone(o) | |
a = SingletonWithCleanInstance() | |
self.assertEqual(a.data, TEST_DATA1) | |
b = SingletonWithCleanInstance() | |
self.assertIs(a, b) | |
self.assertEqual(a.data, TEST_DATA1) | |
self.assertEqual(b.data, TEST_DATA1) | |
b.data = TEST_DATA2 | |
self.assertEqual(a.data, TEST_DATA2) | |
self.assertEqual(b.data, TEST_DATA2) | |
#clear | |
SingletonWithCleanInstance.remove_instance() | |
self.assertIsNone(SingletonWithCleanInstance.get_instance()) | |
self.assertIsNone(a.data) | |
self.assertIsNone(b.data) | |
def test_CachedSingletonWithoutCleanInstance(self): | |
a = CachedSingletonWithoutCleanInstance.get_instance() | |
self.assertIsNone(a) | |
a = CachedSingletonWithoutCleanInstance.get_instance(TEST_ARG1) | |
self.assertIsNone(a) | |
a = CachedSingletonWithoutCleanInstance(TEST_ARG1) | |
self.assertIsInstance(a, CachedSingletonWithoutCleanInstance) | |
self.assertIs(a, CachedSingletonWithoutCleanInstance.get_instance(TEST_ARG1)) | |
self.assertEqual(a.data, TEST_DATA1) | |
#same parameters | |
b = CachedSingletonWithoutCleanInstance(TEST_ARG1) | |
self.assertIs(a, b) | |
self.assertEqual(a.data, TEST_DATA1) | |
self.assertEqual(b.data, TEST_DATA1) | |
b.data = TEST_DATA2 | |
self.assertEqual(a.data, TEST_DATA2) | |
self.assertEqual(b.data, TEST_DATA2) | |
#new parameters | |
b = CachedSingletonWithoutCleanInstance(TEST_ARG2) | |
self.assertIsNot(a, b) | |
self.assertEqual(a.data, TEST_DATA2) | |
self.assertEqual(b.data, TEST_DATA1) | |
#clear | |
CachedSingletonWithoutCleanInstance.remove_instance(TEST_ARG1) | |
self.assertIsNone(CachedSingletonWithoutCleanInstance.get_instance(TEST_ARG1)) | |
self.assertEqual(a.data, TEST_DATA2) | |
CachedSingletonWithoutCleanInstance.remove_instance(TEST_ARG2) | |
self.assertIsNone(CachedSingletonWithoutCleanInstance.get_instance(TEST_ARG2)) | |
self.assertEqual(b.data, TEST_DATA1) | |
def test_CachedSingletonWithCleanInstance(self): | |
a = CachedSingletonWithCleanInstance.get_instance() | |
self.assertIsNone(a) | |
a = CachedSingletonWithCleanInstance.get_instance(TEST_ARG1) | |
self.assertIsNone(a) | |
a = CachedSingletonWithCleanInstance(TEST_ARG1) | |
self.assertIsInstance(a, CachedSingletonWithCleanInstance) | |
self.assertIs(a, CachedSingletonWithCleanInstance.get_instance(TEST_ARG1)) | |
self.assertEqual(a.data, TEST_DATA1) | |
#same parameters | |
b = CachedSingletonWithCleanInstance(TEST_ARG1) | |
self.assertIs(a, b) | |
self.assertEqual(a.data, TEST_DATA1) | |
self.assertEqual(b.data, TEST_DATA1) | |
b.data = TEST_DATA2 | |
self.assertEqual(a.data, TEST_DATA2) | |
self.assertEqual(b.data, TEST_DATA2) | |
#new parameters | |
b = CachedSingletonWithCleanInstance(TEST_ARG2) | |
self.assertIsNot(a, b) | |
self.assertEqual(a.data, TEST_DATA2) | |
self.assertEqual(b.data, TEST_DATA1) | |
#clear | |
CachedSingletonWithCleanInstance.remove_instance(TEST_ARG1) | |
self.assertIsNone(CachedSingletonWithCleanInstance.get_instance(TEST_ARG1)) | |
self.assertIsNone(a.data) | |
CachedSingletonWithCleanInstance.remove_instance(TEST_ARG2) | |
self.assertIsNone(CachedSingletonWithCleanInstance.get_instance(TEST_ARG2)) | |
self.assertIsNone(b.data) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment