Reputation: 5103
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
Reputation: 171
destroy()
as Singleton
's classmethodclass 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 classmethodSimply 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 methodIf 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
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