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 ![]()














One Response to “Python factory-like type instances”
Leave a comment