User Tools

Site Tools


python:misc:class_factory

Python Class Factory

In this tutorial I demonstrate some examples to implement class factories using Python programming language. Examples are based on Python 2.7

You may also read my metaclass tutorial

Example 1

In this example, lets build a class factory. We have two different Connection classes, one for mysql based connections called ConnectionMysql and one for sqlite based connections called ConnectionSqlite for our virtual application. They have same methods but working differently.

class CursorMysql(object):
    pass
 
 
class CursorSqlite(object):
    pass
 
 
class ConnectionBase(object):
    '''This class is our base connection class defining the methods'''
    def cursor(self):
        '''override this'''
 
 
class ConnectionMysql(ConnectionBase):
 
    def __init__(self, username, password, host):
        '''do some mysql stuff here'''
 
    def cursor(self):
        '''
        do some mysql stuff here
        '''
        return CursorMysql()
 
class ConnectionSqlite(ConnectionBase):
 
    def __init__(self, path):
        '''do some sqlite stuff here'''
 
    def cursor(self):
        '''
        do some sqlite stuff here
        '''
        return CursorSqlite()
 
 
def connection(username=None, password=None, host=None, path=None, db="mysql"):
    '''
    This is our factory function which returns appropriate instance according to
    the arguments given and it also asserts the inputs based on the db parameter
    '''
    if db == "sqlite":
        # if db is sqlite, other than path parameter rest must be None
        assert username is None and password is None and path is not None
        return ConnectionSqlite(path=path) # return sqlite connection object
    elif db == "mysql":
        # if db is mysql, only path must be None that is sqlite specific argument
        assert path is None and username is not None and password is not None
        # return mysql connection object
        return ConnectionMysql(username=username, password=password, host=host)
    else:
        raise Exception("Unsupported database")
 
conn = connection(path="db.sqlite", db="sqlite")
print conn
# <__main__.ConnectionSqlite object at 0x0239BC10>
print type(conn)
# <class '__main__.ConnectionSqlite'>
 
conn2 = connection(username="xyz", password="secret", host="192.168.1.10", db="mysql")
print conn2
# <__main__.ConnectionMysql object at 0x0230BCB0>
print type(conn2)
# <class '__main__.ConnectionMysql'>

As can been from the outputs above, factory function returns required instances. You can put classes in factory functions if you want to hide them.

Example 2: Magical __new__

Now, lets follow a different way and use magical method __new__ to implement a new factory but this time we use class to return the correct connection instance. Bear in my that above approach is better than using __new__ as factory. To understand how __new__ works, here is a small example;

class Connection(object):
 
    def __new__(cls, *args, **kwargs):
        print cls, args, kwargs
        return object.__new__(cls, *args, **kwargs)
 
    def __init__(self, *args, **kwargs):
        pass
 
 
print Connection(1, a="a")
# output is
# <class '__main__.Connection'> (1,) {'a': 'a'}
# <__main__.Connection object at 0x023ABD70>

The output shows that __new__ method runs before __init__. First argument of __new__ is cls not self. Other arguments are the same what __init__ takes. Let's see what Python 2 documentation says about __new__

object.__new__(cls[, ...])

Called to create a new instance of class cls. __new__() is a static method (special-cased so you need not declare it as such) that takes the class of which an instance was requested as its first argument. The remaining arguments are those passed to the object constructor expression (the call to the class). The return value of __new__() should be the new object instance (usually an instance of cls).

Typical implementations create a new instance of the class by invoking the superclass’s __new__() method using super(currentclass, cls).__new__(cls[, ...]) with appropriate arguments and then modifying the newly-created instance as necessary before returning it.

If __new__() returns an instance of cls, then the new instance’s __init__() method will be invoked like __init__(self[, ...]), where self is the new instance and the remaining arguments are the same as were passed to __new__().

If __new__() does not return an instance of cls, then the new instance’s __init__() method will not be invoked.

__new__() is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. It is also commonly overridden in custom metaclasses in order to customize class creation.

As the official documentation tells us that, __new__ is responsible for creation of the instance and __init__ is responsible for initializing the instance. So, we can hack the __new__ to return different instance that the class it is in. I use hack term beacuse __new__ is not intended to do this. Here is the factory class. I didn't put the whole example, you can copy it from above.

class Connection(object):
 
    def __new__(cls, username=None, password=None, host=None, path=None, db="mysql"):
        if db == "sqlite":
            # if db is sqlite, other than path parameter rest must be None
            assert username is None and password is None and path is not None
            # return sqlite connection object
            return ConnectionSqlite.__new__(ConnectionSqlite, path=path)
        elif db == "mysql":
            # if db is mysql, only path must be None that is sqlite specific argument
            assert path is None and username is not None and password is not None
            # return mysql connection object
            return ConnectionMysql.__new__(ConnectionMysql, username=username,
                                           password=password, host=host)
        else:
            raise Exception("Unsupported database")
 
 
conn = Connection(path="db.sqlite", db="sqlite")
print conn
# <__main__.ConnectionSqlite object at 0x0250BDD0>
print type(conn)
# <class '__main__.ConnectionSqlite'>
 
conn2 = Connection(username="xyz", password="secret", host="192.168.1.10", db="mysql")
print conn2
# <__main__.ConnectionMysql object at 0x0250BDF0>
print type(conn2)
# <class '__main__.ConnectionMysql'>

Output is as expected so it works as our factory class. This demonstration is just for showing how __new__ method works so I do not prefer to use it as a factory.

Example 3: Using Dict

This is an easy solution. Use a dictionary holding the classes and return the appropriate class as can be seen from the example below.

conn_dict = {"mysql": ConnectionMysql, "sqlite": ConnectionSqlite}
 
connection = conn_dict["mysql"](username="username", password="password", host="192.168.1.10")

Discussion

Enter your comment. Wiki syntax is allowed:
 
python/misc/class_factory.txt · Last modified: 2016/05/22 11:21 (external edit)