Classes and type in Python - Introduction
Tags: python, oop
Published: 2021-07-14 10:14:46

In this post we are going to discuss about Python classes and _type_.

We all know that Python is an object oriented language and here, _everything is an object_.
Everything is an object and is descendant of a class. But what you might have not know is that every class is itself decendant of another class known as _type_.

This might be a little confusing to grasp and i hope the examples here will make it clearer for you.

In python we have the built-in _type()_ function which lets you view the type of any object in python.

Let us consider the example below

```python
name = "Mike"
print(type(name)) # <class 'str'>
```

We see that the type of the name is a string (ofcourse you knew that).
No let us try building our own class.

We are going to build a class called _Fruit_ and we are going to instantiate descendant fruits from our Fruit class.

```python
class Fruit:
    def __init__(self, color, shape):
        self.color = color
        self.shape = shape

    def show_characteristics(self):
        characteristics = [self.shape, self.color]
        return characteristics

```

Fruits have a color, a shape and most of the time are sweet, so we are going to leave taste apart.

We know that we can instatiate a fruit from our fruit class and we have descendant.

Earlier i mentioned that even classes are descendant of another class above them. This means that strings, integers and even out fruit class has a supreme class above it.

## The supreme class

When you inspect the time of an object like an instance of our Fruit or a string object like our name using the _type()_ function, what it returns to you is the parent of that object. What will happen if we try to get the type of our Fruit class itself ?

Execute the code below

```python
class Fruit:
    def __init__(self, color, shape):
        self.color = color
        self.shape = shape

    def show_characteristics(self):
        characteristics = [self.shape, self.color]
        return characteristics

print(type(Fruit))
```

What you get as output is _<class 'type'>_. If you try to execute it on _str_, _int_ and other python classes, you will get _<class 'type'>_. Try executing it on the _type_ too and you will get the same result.

This is because all classes and object inherit from the _type_ class above everything else.

It is therefore possible to create classes in a less cleaner way than above.

We are going to re-create our fruit class directly from _type_.

## Creating a class with _type_

If you look for _type_ in the python help console, you see description of the arguments _type_ takes in order to create a class.

> It is worth noting that _type_ takes either 1 or 3 arguments. When a single argument is passed, it will return the class from which the object inherits. When 3 arguments are passed, it returns to you a new class with the characteristics and attributes you pass in as last argument. More on that below

- The first argument it takes is the name of the class you are creating.
- The second argument is are the bases from which your class would inherit. Say you are creating a new class that inherits from _str_, then you will pass a tuple as second argument representing that
- The third argument is a dictionary that will hold the methods and variables inside our class

Let us now create our class, We are going to recreate the same fruit class as above.

```python
def fruit_init(self, shape, color):
    self.shape = shape
    self.color = color

def show_characteristics(self):
    characteristics = [self.color, self.shape]
    return characteristics

fruit_class = type("FruitClass", (), {"__init__": fruit_init, "show_characteristics": show_characteristics})

```

Let us explore what is happening here.

We are creating a class called **FruitClass**. It inherits from no other class (therefore the empty tuple).
Our class has some methods like _init_ and _show*characteristics*_ which map to the functions we created above.

We created those functions above because it will be hard to fit into the dictionary but you could make use of lambda expressions there.

What we end up with is a class that we can use. Let us try it out

```python
def fruit_init(self, shape, color):
    self.shape = shape
    self.color = color


def show_characteristics(self):
    characteristics = [self.color, self.shape]
    return characteristics


fruit_class = type("FruitClass", (), {
                   "__init__": fruit_init, "show_characteristics": show_characteristics})

orange = fruit_class("spherical", "orange")

print(orange.show_characteristics())
```

Running the code above will print _["spherical", "orange"]_ which means our class worked.

## How is this useful ?

Having knowledge of the above underlying functioning in python will help you understand more in-depth the behaviour of objects and some classes in Python. With this, you can even define your own blueprints for creating other classes and types by just creating a class that inherits from the _type_ class above.

This gives you power to be able to define your own behaviour for objects and even set rules on what happen when a new object is created.

We are going to end here for now and hopefully we will explore this more in-depth and practically next time. I leave it to you to experiment with this knowledge.