Re: Python factory-like type instances

Nicolas: Your metaclass example is a good example of when not to use metaclasses. I wouldn’t be surprised if it is executed slightly different to how you expect. Let’s look at how Foo is evaluated, starting with what’s written:

class Foo:
    __metaclass__ = FooMeta

This is equivalent to the following assignment:

Foo = FooMeta('Foo', (), {...})

As FooMeta has an __new__() method, the attempt to instantiate FooMeta will result in it being called. As the return value of __new__() is not a FooMeta instance, there is no attempt to call FooMeta.__init__(). So we could further simplify the code to:

Foo = {
    'linux2': LinuxFoo,
    'win32': WindowsFoo,
}.get(PLATFORM, None)
if not Foo:
    # XXX: this should _really_ raise something other than Exception
    raise Exception, 'Platform not supported'

So the factory function is gone completely here, and it is clear that the decision about which class to use is being made at module import time rather than class instantiation time.

Now this isn’t to say that metaclasses are useless. In both implementations, the code responsible for selecting the class has knowledge of all implementations. To add a new implementation (e.g. for Solaris or MacOS X), the factory function needs to be updated. A better solution would be to provide a way for new implementations to register themselves with the factory. A metaclass could be used to make the registration automatic:

class FooMeta(type):
    def __init__(self, name, bases, attrs):
        cls = super(FooMeta, self).__init__(name, bases, attrs)
        if cls.platform is not None:
            register_foo_implementation(klass.platform, cls)
        return cls

class Foo:
    __metaclass__ = FooMeta
    platform = None
    ...

class LinuxFoo(Foo):
    platform = 'linux2'

Now the simple act of defining a SolarisFoo class would be enough to have it registered and ready to use.

5 Comments

  1. Posted 12 February, 2008 at 2:42 pm | Permalink

    Nice post. You may have seen this, but FWIW, the following site expands on the idea of ‘…a metaclass could be used to make the registration automatic…’ to build a plugin system.

    http://gulopine.gamemusic.org/2008/jan/10/simple-plugin-framework/

    John

  2. Snark
    Posted 12 February, 2008 at 3:50 pm | Permalink

    Nice post!

    I thought python was saner than C++ — you prove me wrong!

  3. Posted 12 February, 2008 at 9:13 pm | Permalink

    John: that guy really should read up on Zope interfaces a bit more — they handle a lot of the things he is doing.

    Snark: if you think metaclasses are obscure, you should see what people do with sys._getframe().

  4. Posted 13 February, 2008 at 8:47 am | Permalink

    @James,

    Yeah his first paragraph says as much. Still, I applaud his determination for going to the effort. A lesser capable plugin system that doesnt bring in zope.interface might be useful for those people who care about such things

  5. Posted 13 February, 2008 at 10:39 am | Permalink

    There isn’t much point in reinventing zope.interface. With the effort the Zope 3 developers have put in to modularise everything, using zope.interface does not imply pulling in the rest of Zope. There doesn’t seem to be much reason not to use it where appropriate.

One Trackback

  1. By Python if/else in lambda » Ikke’s blog on 17 February, 2008 at 5:47 am

    [...] James, obviously you’re right… Stupid me didn’t think about that. Your version won’t work when a discriminator isn’t known at import time. But even then a function taking *args and **kwargs with a class-like name, returning a correct class instance, would cut the job. [...]