Reputation: 5913
I've created a Singleton using a MetaClass as discussed in Method 3 of this answer
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]
class MySing(metaclass=Singleton): ...
I'd like to be able to clear the Singleton in the setUp()
method of a unittest.TestCase
so that each test starts with a clean Singleton.
I guess I don't really understand what this metaClass is doing because I can't get the correct incantation for a clear()
method:
def clear(self):
try:
del(Singleton._instances[type(self)]
except KeyError:
pass #Sometimes we clear before creating
Any thoughts on what I'm doing wrong here? My singleton is not getting cleared.
sing=MySing()
sing.clear()
The type
call above returns Singleton
not MySing
.
Upvotes: 3
Views: 3859
Reputation: 531345
Let's walk through a (corrected) definition of Singleton
and a class defined using it. I'm replacing uses of cls
with Singleton
where the lookup is passed through anyway.
class Singleton(type):
_instances = {}
# Each of the following functions use cls instead of self
# to emphasize that although they are instance methods of
# Singleton, they are also *class* methods of a class defined
# with Singleton
def __call__(cls, *args, **kwargs):
if cls not in Singleton._instances:
Singleton._instances[cls] = super().__call__(*args, **kwargs)
return Singleton._instances[cls]
def clear(cls):
try:
del Singleton._instances[cls]
except KeyError:
pass
class MySing(metaclass=Singleton):
pass
s1 = MySing() # First call: actually creates a new instance
s2 = MySing() # Second call: returns the cached instance
assert s1 is s2 # Yup, they are the same
MySing.clear() # Throw away the cached instance
s3 = MySing() # Third call: no cached instance, so create one
assert s1 is not s3 # Yup, s3 is a distinct new instance
First, _instances
is a class attribute of the metaclass, meant to map a class to a unique instance of that class.
__call__
is an instance method of the metaclass; its purpose is to make instances of the metaclass (i.e., classes) callable. cls
here is the class being defined, not the metaclass. So each time you call MyClass()
, that converts to Singleton.__call__(MyClass)
.
clear
is also a instance method of the metaclass, meaning it also takes a instance of the meta class (i.e again, a class) as an argument (not an instance of the class defined with the metaclass.) This means MyClass.clear()
is the same as Singleton.clear(MyClass)
. (This also means you can, but probably shouldn't for clarity, write s1.clear()
.)
The identification of metaclass instance methods with "regular" class class methods also explains why you need to use __call__
in the meta class where you would use __new__
in the regular class: __new__
is special-cased as a class method without having to decorate it as such. It's slightly tricky for a metaclass to define an instance method for its instances, so we just use __call__
(since type.__call__
doesn't do much, if anything, beyond invoking the correct __new__
method).
Upvotes: 13
Reputation: 588
I see three useful test cases for this metaclass at first glance.
All of these tests can be achieved without a "reset" button. After which you'll have covered most of your bases. (I might have forgotten one).
Simply create a few different TestClasses that use this metaclass and check their Id's and types.
Upvotes: -2