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.
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
Nice post!
I thought python was saner than C++ — you prove me wrong!
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().
@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
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.
Pingback: Python if/else in lambda » Ikke’s blog