I am trying to create a Singleton class in Python using this code:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
def clear(cls):
cls._instances = {}
class MyClass(metaclass=Singleton):
def my_attribute(*args):
if len(args) == 1:
MyClass.i_attribute = args[0]
elif len(args) == 0:
try:
return MyClass.i_attribute
except:
MyClass.i_attribute = 0
but the clear() method does not seem to work:
MyClass.my_attribute(42)
MyClass.clear()
MyClass.my_attribute() # still returns 42, but I expect 0
How do I delete the instance of MyClass so that I am back to 0 instances?
The singleton metaclass collects in the _instances attribute all the instantiated children. Therefore, if you want to clear the _instances attributes only for a specific class, you can:
Redefine the Singleton class, to make _instances an attribute of the class instantiated by the metaclass:
class Singleton(type):
"""
Singleton metaclass, which stores a single instance of the children class in the children class itself.
The metaclass exposes also a clearing mechanism, that clear the single instance:
* clear: use as follows 'ClassToClear.clear()
"""
def __init__(cls, name, bases, methods):
cls._instance = None
super().__init__(name, bases, methods)
def __call__(cls, *args, **kwargs):
if cls._instance:
return cls._instance
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
def clear(cls):
cls._instance = None
Using this new definition of the singleton, you can write a clear method that can be called by any of the classes initialized with the Singleton metaclass and it will clear the _instance attribute.
So in your case, MyClass.clear() would reset the _instance attribute to None.
Add a clear method, which removes only the children class from the Singleton._instances dictionary:
class SingletonRegistry(type):
"""
Singleton metaclass, which implements a registry of all classes that are created through this metaclass and
the corresponding instance of that class (added at the first creation).
The metaclass exposes also a clearing mechanism, that clears a specific class from the registry:
* clear: use as follows 'ClassToClear.clear()
* clear_all: use as follows 'SingletonRegistry.clear_all()
"""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonRegistry, cls).__call__(*args, **kwargs)
return cls._instances[cls]
def clear(cls):
_ = cls._instances.pop(cls, None)
def clear_all(*args, **kwargs):
SingletonRegistry._instances = {}
In this case, if you would like to clear only one specific child class, then you could write MyClass.clear(), which will cause the MyClass key to be removed from Singleton._instances dictionary.
This structure allows also to clear all key in the _instances dictionary by writing SingletonRegistry.clear_all().
user2357112 is right. Here is the correct code:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
def clear(cls):
cls._instances = {}
class MyClass(metaclass=Singleton):
def my_attribute(*args):
my = MyClass()
if len(args) == 1:
my.i_attribute = args[0]
elif len(args) == 0:
try:
return my.i_attribute
except:
my.i_attribute = 0
return my.i_attribute
Related
I'm trying to prepare a singleton class that would distinguish the instances not only by class types, but also by arguments with which the class was called.
Let's say I've a Singleton class like below:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
And now I'm trying to create two class instances with different parameters:
class MyClass(metaclass=Singleton):
def __init__(self, x, y):
print(f"Called constructor with x as {x} and y as {y}")
a = MyClass(1, 2)
b = MyClass(1, 2)
print(id(a) == id(b)) # Returns True, which is fine
c = MyClass(1, 3)
d = MyClass(1, 2)
print(id(c) == id(d)) # Returns True, which is not really fine to me :)
# Moreover y in that case is 3, not 2.
What in case I want to distinguish instances in singleton additionally by parameters with which the class was initialized?
Here is what worked for me:
from dataclasses import dataclass
#dataclass(frozen=True)
class ObjectKey:
obj_type: object
args: tuple
kwargs: frozenset
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
obj_key = ObjectKey(obj_type=cls, args=args, kwargs=frozenset(kwargs.items()))
if obj_key not in cls._instances:
cls._instances[obj_key] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[obj_key]
I need to keep tracks of instances of some classes (and do other stuff with those classes). I would like to not have to declare any extra code in the classes in question, thus everything should ideally be handled in the metaclass.
What I can't figure out is how to add a weak reference to each new instance of those classes. For example:
class Parallelizable(type):
def __new__(cls, name, bases, attr):
meta = super().__new__(cls, name, bases, attr)
# storing the instances in this WeakSet
meta._instances = weakref.WeakSet()
return meta
#property
def instances(cls):
return [x for x in cls._instances]
class Foo(metaclass=Parallelizable)
def __init__(self, name):
super().__init__()
self.name = name
# I would like to avoid having to do that - instead have the metaclass manage it somehow
self._instances.add(self)
Any ideas? I can't seem to find a hook on the metaclass side to get into the __init__ of Foo....
The method on the metaclass that is called when each new instance of its "afiliated" classes is __call__. If you put the code to record the instances in there, that is all the work you need:
from weakref import WeakSet
# A convenient class-level descriptor to retrieve the instances:
class Instances:
def __get__(self, instance, cls):
return [x for x in cls._instances]
class Parallelizable(type):
def __init__(cls, name, bases, attrs, **kw):
super().__init__(name, bases, attrs, **kw)
cls._instances = WeakSet()
cls.instances = Instances()
def __call__(cls, *args, **kw):
instance = super().__call__(*args, **kw)
cls._instances.add(instance)
return instance
The same code will work without the descriptor at all - it is just a nice way to have a class attribute that would report the instances. But if the WeakSet is enough, this code suffices:
from weakref import WeakSet
class Parallelizable(type):
def __init__(cls, name, bases, attrs, **kw):
super().__init__(name, bases, attrs, **kw)
cls.instances = WeakSet()
def __call__(cls, *args, **kw):
instance = super().__call__(*args, **kw)
cls.instances.add(instance)
return instance
You could decorate the attrs['__init__'] method in Parallizable.__new__:
import weakref
import functools
class Parallelizable(type):
def __new__(meta, name, bases, attrs):
attrs['__init__'] = Parallelizable.register(attrs['__init__'])
cls = super().__new__(meta, name, bases, attrs)
cls._instances = weakref.WeakSet()
return cls
#classmethod
def register(cls, method):
#functools.wraps(method)
def newmethod(self, *args, **kwargs):
method(self, *args, **kwargs)
self._instances.add(self)
return newmethod
#property
def instances(cls):
return [x for x in cls._instances]
class Foo(metaclass=Parallelizable):
def __init__(self, name):
"Foo.__init__ doc string"
super().__init__()
self.name = name
# Notice that Foo.__init__'s docstring is preserved even though the method has been decorated
help(Foo.__init__)
# Help on function __init__ in module __main__:
#
# __init__(self, name)
# Foo.__init__ doc string
stilton = Foo('Stilton')
gruyere = Foo('Gruyere')
print([inst.name for inst in Foo.instances])
# ['Gruyere', 'Stilton']
del stilton
print([inst.name for inst in Foo.instances])
# ['Gruyere']
How about this, its a class to inherit from, instead of a metaclass. I think its simpler but achieves the same point:
class AutoDiscovered:
instances = []
def __new__(cls, *args, **kwargs):
obj = super().__new__(cls)
cls.instances.append(obj)
return obj
Usage:
class Foo(AutoDiscovered):
pass
a = Foo()
b = Foo()
print(Foo.instances) # [<__main__.Foo object at 0x7fdabd345430>, <__main__.Foo object at 0x7fdabd345370>]
I'm trying to extend my python knowledge. So I just wrote my very first singleton metaclass:
class Singleton(type):
_instance = None
def __call__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
I just checked (for feedback) the good old stackoverflow. Lets see 'how others do it' and I found this solution:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
Can somebody explain me why (the hack) do we need that dictionary?
This is to support inheritance. Using your solution, inheriting from a class built with the Singleton metaclass does not allow the subclass to have its own singelton.
class Singleton(type):
_instance = None
def __call__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class FirstSingleton(metaclass=Singleton):
pass
class SecondSingleton(FirstSingleton):
pass
x = FirstSingleton()
y = SecondSingleton()
x is y # True
As you see, the calls FirstSingleton() and SecondSingleton() both returned the same instance.
But using a dictionary allows a class and its subclasses to have different singletons.
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class FirstSingleton(metaclass=Singleton):
pass
class SecondSingleton(FirstSingleton):
pass
x = FirstSingleton()
y = SecondSingleton()
x is y # False
The class and the subclass each returned their own instance of a singleton.
I have a class that I want it to accept an instance of that same class as initialization; in such case, it will simply return that instance.
The reason is that I want this class to accept a myriad of initialization values and then the proceeding code can use this as an object with known properties, independent on how it was initialized.
I have thought of something like:
class c(object):
def __new__(cls, *args, **kwargs):
if isinstance(args[0], c):
return args[0]
else:
return super(c, cls).__new__(cls, *args, **kwargs)
The problem is that I don't want __init__() to be called when initialized in this manner. Is there any other way?
Thanks!
You probably want to use a factory (f.e. see this question for details or google).
Or just use a class method for what you want, f.e.:
class C(object):
#classmethod
def new(cls, *args, **kwargs):
if isinstance(args[0], cls):
return args[0]
else:
return cls(*args, **kwargs)
obj = C.new()
obj2 = C.new(obj)
The standard way to do this is to simply not do your initialization in __init__. Do it in __new__.
You can use a metaclass
class InstanceReturnMeta(type): # You should probably think of a better name
def __call__(cls, *args, **kwargs):
if args and isinstance(args[0], cls):
return args[0]
instance = cls.__new__(cls, *args, **kwargs)
instance.__init__(*args, **kwargs)
return instance
class Test(object):
__metaclass__ = InstanceReturnMeta
def __init__(self, value):
self.value = value
Let's test it
In [3]: instance1 = Test(0)
In [4]: instance2 = Test(instance1)
In [5]: print id(instance1) == id(instance2)
Out[5]: True
The ids are identical, hence both variables reference the same instance.
P.S. I assume you are on Python 2, since your class explicitly inherits from object.
As answered brilliantly by agf here, a possible implementation of a Singleton in Python could be using this metaclass:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
However, I do not understand the use of _instance. I know it's a dict used to store instances indexed by their class. However, it is declared as a class attribute (_instances = {}), and it is used as an attribute of cls (if cls not in cls._instances:). How can the two be the same?