Singletons and Other Unique Objects in Python

  • Print

A brief depiction of the problem. As I was developing a backend for my Python game, I created a class for game objects which could create them from database records (in my case, MongoDB documents) and embed into the game core. And then I faced the problem of uniqueness of my objects. With many clients connecting to the backend, it is possible that some objects could be requested from database multiple times. But if I had allowed to create multiple objects for same entity, I would have surely encountered problems in my game logic.

I could surely create some dict which could contain references to created objects, but how to do that in a nice way? Here is the solution I've came to.

I have already wrote that  metaclasses are awesome pythonic tool that allows to change completely object creation logic. Python __new__ methods can't do that. Consider the following code:

class A(object):
    objects_by_unique_data = {}
    def __new__(cls, data):
        if data in cls.objects_by_unique_data:
            return cls.objects_by_unique_data[data]
            result = object.__new__(cls)
            cls.objects_by_unique_data[data] = result
            return result
    def __init__(self, data):
         print "Hi there! I'm doing stuff!"

Yeah, you can save some memory using this. But actually __init__ method will run every time you create an object, even if the data is doubled. So what can we do?

Here is the solution using Python metaclasses.

from weakref import WeakValueDictionary

class metaA(type):
    def __call__(self, data):
        if data in self.objects_by_unique_data:
            return self.objects_by_unique_data[data]
            result = type.__call__(self, data)
            self.objects_by_unique_data[data] = result
            return result

class A(object):
    __metaclass__ = metaA
    objects_by_unique_data = WeakValueDictionary()

    #def __new__(...) - not needed!

    def __init__(self, data):
        print "I am now sure I'm unique. ^__^"

So, the call to A(...) itself is redefined! There is no need in __new__ at all now, as all the work is done in type(A).__call__. Also note use of WeakValueDictionary. This dictionary contains auto-deleted weak references to objects, thus your objects will be garbage-collected if you won't need them in your program core. If we used dict, it would keep the strong references forever, disallowing garbage collector to clean up the house.

Python is awesome.