Multiton again, in Python

May 18, 2008 – 1:33 am

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. :)

  1. 2 Responses to “Multiton again, in Python”

  2. 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.

    By Mithro on May 18, 2008

  3. @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. :)

    By pluskid on May 21, 2008

Post a Comment