Skip to content

Instantly share code, notes, and snippets.

@wellic
Last active November 29, 2019 12:40
Show Gist options
  • Save wellic/72a8702e42c4b345f9149ef679bf5dce to your computer and use it in GitHub Desktop.
Save wellic/72a8702e42c4b345f9149ef679bf5dce to your computer and use it in GitHub Desktop.
Example python 3.6+ Singleton
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
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