Singleton that instantiates a class by its object and arguments - python

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]

Related

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

python singleton metaclass dict vs non dict

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.

__repr__ decorator fails on subclasses

Background
I wrote a decorator function to modify the __repr__ of a given class, such that when an class instance is called all its attributes are printed to the user. When used in the on the Container class in the example below the decorator __repr__dec behaves as intended.
Input
def __repr__wrapper(self):
"""Show all attributes."""
return "Attributes: "+", ".join(list(self.__dict__.keys()))
def __repr__dec(func):
"""Replaces the __repr__ function of a class with __repr__wrapper"""
def call(*args, **kwargs):
func.__repr__ = __repr__wrapper
result = func(*args, **kwargs)
return result
return call
#__repr__dec
class Container(object):
def __init__(self, *args, **kwargs):
self.metadata = args[0]
for k,v in kwargs.items():
self.__dict__[k] = v
occ = Container(42, how="now")
occ
Output
Attributes: metadata, how
However when trying to subclass Container I receive a TypeError message:
Input
class Handle(Container):
def __init__(self, *args, **kwargs):
Container.__init__(self, *args, **kwargs)
han = Handle(42)
Output
TypeError Traceback (most recent call last)
<ipython-input-17-b4c252411c1f> in <module>()
----> 1 class Handle(Container):
2 def __init__(self, *args, **kwargs):
3 Container.__init__(self, *args, **kwargs)
4
5 han = Handle(42)
TypeError: function() argument 1 must be code, not str
Question
Why does sub-classing Conatainer fail when using the __repr__dec function? Is it possible to fix this?
The problem is that your decorator made Container a function and no longer a class. You can control it very simply:
>>> type(Container)
<class 'function'>
This is because your use of the decorator ends in the following:
declare a undecorated class
class Container:
...
use the decorator on it:
Container = __repr__dec(Container)
As __repr__dec returns a function you have indeed change Container into a function able to return objects having the expected __repr__ member, but it is no longer a class.
Your decorator must return a class if you want to be able to later subclass it:
def repr_dec(cls):
cls.__repr__ = __repr__wrapper
return cls
Then everything is fine:
>>> Container
<class '__main__.Container'>
>>> occ=Container(42, how="now")
>>> occ
Attributes: metadata, how
And you can successfully subclass it:
>>> class Handle(Container):
def __init__(self, *args, **kwargs):
Container.__init__(self, *args, **kwargs)
>>> han = Handle(42, foo="now")
>>> han
Attributes: metadata, foo
Handle class has inherited the __repr__ method from its parent.
def replace_str(cls):
class Wrapper:
def __init__(self, *args, **kargs):
self.wrapped = cls(*args, **kargs)
def __getattr__(self, attrname):
return getattr(self.wrapped, attrname)
def __str__(self):
return "Attributes: " + ", ".join(list(self.wrapped.__dict__.keys()))
return Wrapper
#replace_str
class Container(object):
def __init__(self, *args, **kwargs):
self.metadata = args[0]
for k,v in kwargs.items():
self.__dict__[k] = v
Using a proxy class could easily achieve this.
also, metaclass could do this:
class PrintKey(type):
def __new__(meta, classname, bases, namespace):
namespace['__str__'] = lambda self: "Attributes: " + ", ".join(list(self.__dict__.keys()))
return type.__new__(meta, classname, bases, namespace)
class Container(object, metaclass=PrintKey):
def __init__(self, *args, **kwargs):
self.metadata = args[0]
for k,v in kwargs.items():
self.__dict__[k] = v

Best python way to return the initializing value of class if of that same class

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.

Categories

Resources