Creating a singleton for each instance of a class

Viewed 113

I have a python class, let's call it C. it takes a string in it's constructor. how could i make sure (design-wise) that

x = C('a')
y = C('b')
z = C('a')

this configuration leads to x and y pointing to different places, and x and z pointing to the same place? meaning

x is y == False
x is z == True
2 Answers

You can use a WeakValueDictionary to keep all existing instances of your class in a dict like object on the class. In the __new__ method you look up the existing object in the dict and if it exists return it, if not create a new instance and save it to the dict

from weakref import WeakValueDictionary


class C:

    __instances = WeakValueDictionary()

    def __new__(cls, value):
        obj = cls.__instances.get(value)
        if not obj:
            obj = object.__new__(cls)
            cls.__instances[value] = obj
        return obj

    def __init__(self, value):
        self.value = value

We use weak references to the instances so that they can be garbage collected when nothing else references them.

One lazy way to do this would be to annotate the class itself with functools.lru_cache:

from functools import lru_cache

@lru_cache(None)
class C:
    def __init__(self, val):
        self.val = val

x = C('a')
y = C('b')
z = C('a')

print(x is y)  # False
print(x is z)  # True

lru_cache is actually intended for caching functions, but it works with any callable, including classes, where it caches the call to C.__init__. Parameters must be hashable, of course.

This way, as far as I know, instances are not garbage-collected even if they are no longer used, but you could control the maximum number of instances to cache with the parameter to lru_cache (None meaning "no limit"), which might be good enough for many cases.

Related