user1742188
user1742188

Reputation: 5103

Destroying a Singleton object in Python

I have a Singleton object in Python:

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]

    @classmethod
    def destroy(cls):
        del cls._instances[cls]


class MockObject(metaclass=Singleton):

    def __init__(self, *args, **kwargs):
        # various things

I would like to destroy the object at some point, so I wrote a classmethod in the metaclass. However, the cls refers to the metaclass Singleton rather than MockObject. Is there a way to call the destroy function with a value of MockObject?

Upvotes: 13

Views: 12300

Answers (2)

ZHANG Wuji
ZHANG Wuji

Reputation: 171

destroy() as Singleton's classmethod

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]

    @classmethod
    def destroy(metacls, cls):
        if cls in metacls._instances:
            del metacls._instances[cls]


class MockObject(metaclass=Singleton):
    def __init__(self, *args, **kwargs):
        pass


if __name__ == '__main__':
    m = MockObject()
    print(Singleton._instances)
    
    # call `destroy()` at metaclass level with subclass as argument
    Singleton.destroy(MockObject)
    print(Singleton._instances)
    
    # when called after destroy() 
    # a new instance is created at a different memory location
    m = MockObject()
    print(Singleton._instances)

Output:

{<class '__main__.MockObject'>: <__main__.MockObject object at 0x1004b6660>}
{}
{<class '__main__.MockObject'>: <__main__.MockObject object at 0x1004b6690>}

destroy() as subclass's classmethod

Simply without @classmethod:

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 destroy(cls):
        # NOTE: cls._instances is reference to Singleton._instances
        if cls in cls._instances:
            del cls._instances[cls]


class MockObject(metaclass=Singleton):
    def __init__(self, *args, **kwargs):
        pass


if __name__ == '__main__':
    m = MockObject()
    print(Singleton._instances)

    # call `destroy()` at subclass level
    MockObject.destroy()
    print(Singleton._instances)
    
    m = MockObject()
    print(Singleton._instances)

Output:

{<class '__main__.MockObject'>: <__main__.MockObject object at 0x104696600>}
{}
{<class '__main__.MockObject'>: <__main__.MockObject object at 0x104696630>}

The classes defined use metaclass are instances of metaclass, so the instance method of metaclass are inherited as classmethod of regular class, i.e. classmethod of MockObject is instance method of Singleton.

destroy() as subclass's instance method

If you need define a destroy method in MockObject's instances, you'd have to operate in the __new__ method of metaclass.

class Singleton(type):
    _instances = {}

    def __new__(cls, name, bases, namespace):
        namespace.update(destroy=Singleton.destroy)
        return super().__new__(cls, name, bases, namespace)

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

    @staticmethod
    def destroy(self):
        cls = self.__class__
        if cls in cls._instances:
            del cls._instances[cls]


class MockObject(metaclass=Singleton):
    def __init__(self, *args, **kwargs):
        pass


if __name__ == '__main__':
    m = MockObject()
    print(Singleton._instances)
    
    # call `destroy()` at instance level
    m.destroy()
    print(Singleton._instances)

    m = MockObject()
    print(Singleton._instances)

Output:

{<class '__main__.MockObject'>: <__main__.MockObject object at 0x10431aa20>}
{}
{<class '__main__.MockObject'>: <__main__.MockObject object at 0x10431aa50>}

Upvotes: 0

Ashwini Chaudhary
Ashwini Chaudhary

Reputation: 250981

Instead of defining a custom method for deleting the instance reference use a WeakValueDictionary.

Now when there are no more references of MockObject anywhere it will be cleaned up from Singleton._instances automatically.

from weakref import WeakValueDictionary


class Singleton(type):
    _instances = WeakValueDictionary()

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            # This variable declaration is required to force a
            # strong reference on the instance.
            instance = super(Singleton, cls).__call__(*args, **kwargs)
            cls._instances[cls] = instance
        return cls._instances[cls]


class MockObject(metaclass=Singleton):

    def __init__(self, *args, **kwargs):
        pass


if __name__ == '__main__':
    m = MockObject()
    print(dict(Singleton._instances))
    del m
    print(dict(Singleton._instances))

Output:

{<class '__main__.MockObject'>: <__main__.MockObject object at 0x104531128>}
{}

Upvotes: 27

Related Questions