Multiton again, in Python

I have introduced Multiton in one of my previous blog post. Multiton is just like Singleton, except that there will be multiple instance when the init parameters are different. One example is the Lisp symbol:

  • Different symbol object for different symbol name.
  • Identical symbol object for identical symbol name.
The last time I was implementing a Multiton is when I wrote my Scheme interpreter in Ruby. It is fairly easy in Ruby, I wrote a Multiton module, just include that module, your class becomes a Multiton.

But now (when doing Schemepy) I have to do the same thing again in Python. In Ruby the relationship between instance and its class, class and its class (class's class) is identical. However, there are metaclass in Python, I'm some what confusing about that currently. I know both __init__ and __new__ is not enough for such a task. But I don't know how to deal with metaclass.

After discussing this with Mike after today's salon, he gave me a sample code (for Singleton):


class MyAtt(type):
    def __call__(self, *args, **kwargs):
        if self.inst != None:
            return self.inst

obj = type.__call__(self, *args) for name in kwargs: setattr(obj, name, kwargs[name]) self.inst = obj return obj

class Car(object): __metaclass__ = MyAtt inst = None

c = Car() b = Car()

print id(b), id(c)

Then I understood immediately! That's exactly what I was looking for! :D So I write the Symbol Multiton:


import weakref

class Symbol(object): class _Meta(type): def __call__(cls, name): if cls.symbols.has_key(name): return cls.symbols[name]

sym = type.__call__(cls, name) cls.symbols[name] = sym return sym

__metaclass__ = _Meta symbols = weakref.WeakValueDictionary({})

def __init__(self, name): self._name = name

def __eq__(self, other): return self is other

def get_name(self): return self._name def set_name(self): raise AttributeError("Can't modify name of a symbol.") name = property(get_name, set_name)

if __name__ == '__main__': sym1 = Symbol("foo") sym2 = Symbol("foo")

assert sym1 == sym2 assert sym1 is sym2

As the Ruby Multiton module, I can make the metaclass a generic one. Though still not so flexible as Ruby (you can't have multiple metaclass), it's still awesome! It seems I'm starting to change my altitude to Python. :)

Comments (2)

Mithro May 18, 2008

While this is fairly ingenious, this it not the standard way of doing this. First, let me explain the difference between __new__ and __init__.

__new__ method is called before the any memory or structure has been allocated for the object. It is the job of this method to create the object.

__init__ method is called after the object has been created and is in charge of initializing the object.

By overriding __new__ you can create singletons/multitons/or anything in between. Have a look at the following file for a number of uses of the __new__ method. The Game class acts very much like your multiton class.

What you have to be careful of, is that __init__ gets call even when you return an existing object from __new__. Generally people only have a __new__ or an __init__ method.

Btw You can have multiple metaclasses, you just need to do it in two steps.

class A(object):
    pass

class B(object): pass

class AB(A, B): pass

class C: """ This class has both A and B as it's metaclasses! """ __metaclass__ = AB

While Python is less flexible then ruby, it's generally less flexible for good reasons. The above multiple metaclasses case makes it clear which methods will be called (as it follows the standard multiple inheritance flow). If you read the Zen of Python you will see why.

BTW @staticmethod and @classmethod are both metaclass programing too.

pluskid May 21, 2008

@Mithro,

I see. So this is enough for the multiton:

    def __new__(cls, name):
        try:
            return cls.symbols[name]
        except KeyError:
            sym = object.__new__(cls)
            sym._name = name
            cls.symbols[name] = sym
            return sym

Unless we should ensure __init__ will be called each time. :)