When designing applications or libraries, sometimes you need to be able to create instances of a certain interface (in a liberal sense) at runtime without knowing at write/compile time which specific implementation (class) you’ll need to use, as this could depend on runtime variables.
An example of this is an interface providing some functionality which should be implemented differently on different platforms, eg Linux and Windows.
There are some standard patterns how to achieve this. One of them is the factory pattern, which works somewhat like this Python example (let’s pretend ‘PLATFORM’ is ‘linux2′ or ‘win32′, ie sys.platform):
#Pretend we use sys.platform instead of PLATFORM where we use it PLATFORM = 'linux2' class FooBase(object): def say_foo(self): print 'foo' class PlatformFoo(FooBase): def say_platform_foo(self): raise NotImplementedError @staticmethod def get_class(): #Several ways to get this (dict, introspection, if-tree,...), pick yours klass = { 'linux2': LinuxFoo, 'win32': WindowsFoo, }.get(PLATFORM, None) if not klass: raise Exception, 'Platform not supported' return klass class WindowsFoo(PlatformFoo): def say_platform_foo(self): print 'win32 foo' class LinuxFoo(PlatformFoo): def say_platform_foo(self): print 'linux foo' def main(): foo_class = PlatformFoo.get_class() foo = foo_class() foo.say_platform_foo() if __name__ == '__main__': main()
Executing this code will, as expected, write ‘linux foo’ to the console. Obviously we could not return the platform-specific class in a PlatformFoo function, but an actual instance, up to you.
Python allows you to handle this situation somewhat nicer though, without introducing any intermediate functions, by using metaclasses.
I won’t explain what metaclasses are here, or how they work, there are several resources on the internet explaining them. Let’s just get to the code:
#Pretend we use sys.platform instead of PLATFORM where we use it PLATFORM = 'linux2' class FooBase(object): def say_foo(self): print 'foo' def say_platform_foo(self): raise NotImplementedError class WindowsFoo(FooBase): def say_platform_foo(self): print 'win32 foo' class LinuxFoo(FooBase): def say_platform_foo(self): print 'linux foo' class FooMeta(type): def __new__(cls, name, bases, attrs): #Several ways to get this (dict, introspection, if-tree,...), pick yours klass = { 'linux2': LinuxFoo, 'win32': WindowsFoo, }.get(PLATFORM, None) if not klass: raise Exception, 'Platform not supported' return klass class Foo: __metaclass__ = FooMeta def main(): foo = Foo() foo.say_platform_foo() if __name__ == '__main__': main()
See we don’t need any getter-function here, but we can just create a ‘Foo’ instance? The resulting object will be a ‘LinuxFoo’ or a ‘WindowsFoo’ as expected, depending on the value of ‘PLATFORM’. The above code also displays ‘linux foo’.
There’s something nifty about it too: you don’t loose any class inheritance information:
In [1]: from test2 import Foo, LinuxFoo, FooBase In [2]: f = Foo() In [3]: f.say_platform_foo() linux foo In [4]: type(f) Out[4]:In [5]: isinstance(f, LinuxFoo) Out[5]: True In [6]: isinstance(f, FooBase) Out[6]: True In [7]: isinstance(f, Foo) Out[7]: True
This shouldn’t surprise you, as ‘Foo’ actually became an alias for ‘LinuxFoo’.
Maybe this pattern will be useful in one of your projects one day, who know
I don’t know.. I prefer the below example mainly because it is without metaclass hackery (Foo magicly has some functionality but does not inherit from anything), but not quite so different:
So if you were to do what you said but instead had WindowsFoo and Linux foo be in two different files, how could you achieve the same result? __subclasses__() is empty if WindowsFoo and LinuxFoo are not in the same file
I’m asking because I’m trying to implement the same sort of idea but I want my subclasses to be in different files