Class Creation in Python

2023-03-07

In python, all classes are an instance of an other class. An user-defined class is an instance of type class.

class Foo:
    
    def __init__(self, a):
        self.a = a 

    def get_a(self):
        return self.a

    def set_a(self, a):
        self.a = a

For the above class Foo, print (type(Foo)) shows <class 'type'>. The class keyword creates an instance of type object which can be identified using Foo. A class object also allows to makes instances of it's type by using its identifier (Foo).

Here is an another way to create a class object by directly creating an instance of type class.


def get_a(arg1):
    return arg1.a

def set_a(arg1, i):
    arg1.a = i

def __init__(arg1, a):
    arg1.a = a

Foo = type('Foo', (), dict(get_a=get_a, set_a=set_a, 
          __init__=__init__))

Here, we directly invoke type() constructor to create the class object. The type() constructor has the signature type(name, bases, namespace). name is a string giving the name of the class to be constructed, bases is a tuple which identifies the parent classes of the class to be constructed and namespace is a dictionary of attributes and methods of the class to be constructed.

Customising class creation

Using __new__ method

__new__ can be used to control creation of object. For example, to implement a singleton object, we can use __new__. Here is another example of using __new__ object:

class Number(tuple):

    def __new__(cls, *args):
        if not isinstance(args[0], int):
            raise ValueError('argument should be an integer')

        if (args[0] % 2 == 0):
            return tuple.__new__(tuple, (args[0], 'even'))
        elif (args[0] % 2 == 1):
            return tuple.__new__(tuple, (args[0], 'odd'))

a = Number(1)  # `a` stores (1, 'odd')
b = Number(2)  # `b` stores (2, 'even')

In the above example, we customise the creation of a tuple. The __new__ method is used to customise the creation of tuple since once a tuple is created, it cannot be modified.

__new__ creates a new instance of the class. The __new__ method is the different from __init__. It creates the actual class object while the __init__ method customizes the created object.

Customising via metaclass

The class of a class is knows as metaclass. Most classes have type as their metaclass as type is the class which is used to create the class.

We can customise class creation by using the metaclass keyword in class definition or by inheriting from an existing class that included such argument.

A metaclass can customise preparation of class namespace and resolving of method resolution order.

Example

class Meta(type):
    pass

class Foo(metaclass=Meta):

    def __init__(self):
        pass

if __name__ == "__main__":
    print ('type of Foo is ', type(Foo))  # shows `<class '__main__.Meta'>`
    print ('type of Meta is ', type(Meta))  # shows `<class 'type'>`.

The above snippet shows <class '__main__.Meta'> and <class 'type'>. I leave it to a future post for covering customisation of class creation via metaclasses.

__init_subclass__ hook

The __init_subclass__ hook when defined in the parent class of a class can modify attributes in the subclass, thus changing the subclass's behavior. For more details, I refer the reader to python documentation

References

Acknowledgemnets

A special thanks to Rahul Sai Poruri and Tony Davis for discussions and feedbacks on earlier versions of the draft.