user3878681
user3878681

Reputation: 61

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?

Upvotes: 3

Views: 5351

Answers (2)

buendnerbaer
buendnerbaer

Reputation: 61

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:

  1. 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.

  1. 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().

Upvotes: 6

user3878681
user3878681

Reputation: 61

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

Upvotes: 0

Related Questions