python singleton metaclass dict vs non dict - python

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.

Related

Singleton that instantiates a class by its object and arguments

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]

Why is singleton class' __init__() method called twice?

Why foo is printed twice?
class A:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, *, var):
print('foo')
self.var = var
a = A(var=1)
b = A(var=2)
assert a == b
assert a.var == b.var
When you write A(...), you are calling
type.__call__(A, ...)
That's because type is the metaclass of A. type.__call__ will call __new__ and __init__ in sequence. __init__ will always be called if __new__ returns an instance of the class. Here is a simplified view:
def __call__(cls, *args, **kwargs):
self = cls.__new__(cls, *args, **kwargs)
if isinstance(self, cls):
cls.__init__(self, *args, **kwargs)
return self
The simplest way I can think of making a singleton work this way is to put all your initialization logic into __new__:
class A:
_instance = None
def __new__(cls, *, var):
if cls._instance is None:
self = super().__new__(cls)
self.var = var
cls._instance = self
return cls._instance
def __init__(self, *args, **kwargs):
print('foo')
Sure, foo still gets printed every time you ask for a new instance, but it's well and truly a singleton.
A more comprehensive approach is to override the behavior of your metaclass __call__ method. That will avoid calling __init__ except once:
class Singleton(type):
def __call__(cls, *args, **kwargs):
if hasattr(cls, '_instance'):
return cls._instance
return super().__call__(*args, **kwargs)
class A(metaclass=Singleton):
def __init__(self, *, var):
print('foo')
self.var = var

How to reset a singleton instance in python in this case?

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

Using metaclass to keep track of instances in python

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>]

Implementation of the Singleton pattern in Python

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?

Categories

Resources