Python Classes

author-image  By Dhiraj, 19 March, 2019   0K

In this article, we will be discussing the object-oriented paradigm in Python using class. We will learn how to create a custom data type with class keyword in Python and it's usage with multiple examples. We will take a look into how constructor invocation flow is implemented in Python and the background job Python runtime does during class instantiation with __init__(). We will be also discussing OOPS principles such as Inheritance and Polymorphism.

Everything in Python is an Object as Python is an object-oriented programming language. An object is an instance of Class and class is a template that describes the kinds of state and behavior that objects of its type support. Till now, we have encountered many built-in classes throughout the previous python tutorials such as int, str, list etc and Python provides tons of it. Then why we need to create new classes. The power of creating a new class is required to create custom types. We can use built-in classes with our custom types and provide a very robust, dynamic and generic solution to any problem.

State - Each instance of a class(object) has it's own instance variable defined and the value assigned to those variable represents the object state.

Behaviour - The functions defined in a class represents the class behaviour and these functions are called methods. Methods are where the class logic is stored.

In object-oriented programming paradigm, everything is an object. A bird, car, human or entire universe can be treated as an object with our custom state and behavior stuffed inside it and this state and behavior can be used or re-used to provide a dynamic solution to the problem. For example, if we assume a car to be an Object then the tyres, steering, mirror, brakes can be assumed as a state of the object and actions such as apply_brake(), move_tyre() can be assumed as behavior. The core logic of a class resides into methods.

With the help of class, we can design solutions of complex problems in a simpler manner with less amount of code. For example, we can use Inheritance, which allows code defined in one class to be reused in other classes. Suppose, the car object that we defined above is a very basic old style car. Now, we want to add power steering to it. To do so we can create a new sub class with inheritance and override the exisitng methods or add new methods to create a power steering.

Another feature, we can use is Polymorphism which allows the usage of the same method name in different forms. So far, we have come across to len() method in different forms such as len("string") or len([2, 4, 5]) - same method in different forms.

Class Definition Syntax

We create Class using class keyword followed by the class name and colon(:) in Python. Inside the class definition, you can have your functions and instanc variables described. By convention, class name uses camelcase.

class ClassName:
    
    .
    .
    .
    

The statements inside a class definition are usually be function definitions, but other statements are allowed that begins with double underscores (__) such as __init__.

class Car:
    __doc__ = "A car class to create a custom car type."

	# Only requires in case we want to perform any operation during object instantiation
    def __init__(self): # reference to object of Car class
        print(type(self))

Object Instantiation in Python

We use constructor to instantiate an object of a class. During instantiation, we can use __init__ method to supply any extra parameters required to instantiate a class if required. These parameters can be used to initiliase the instance variables. We can instantiate our Car class as below.

from classexample import Car
 #creates a new instance of the class and assigns this object to the local variable car
car = Car()


The __init__ method accepts self as a parameter to instantiate the Car class but we have not provided any dring instantiation as Python does that for us. By rule, self is the first argument to all instance methods.

Now, let us pass some parameters during object instantiation.

...

    def __init__(self, name, regd_num):
	self._name = name
	self._regd_num = regd_num
	print("Name of the car is '{}' with registration number '{}'".format(name, regd_num))

To instantiate the car object, we need to invoke car = Car("abc", "ABC123"). The previous statement car = Car() is syntatically incorrect now.

As we do not need to declare a variable untill we create them neither we need to declare object variable untill we create it.

By convention, implementation details start with underscore which are not intended to be manipulated by outside. There is no concept of public, private or protected in Python unlike Java.

Now let us add methods in our class definition. Below is our final class structure.

class Car:
    __doc__ = "A car class to create a custom car type."
	
	class_var = [] # class variable. Don't get confused with instance variable.
	
    def __init__(self, name, regd_num):
        self._name = name
        self._regd_num = regd_num
        print("Name of the car is '{}' with registration number '{}'".format(name, regd_num))

    def getName(self):
        return self._name

    def getRegdNumber(self):
        return self._regd_num
    

Accessing Instance Variables and Methods

As discussed above, we don't need to declare object variable untill we create it. To access an instance variable, we just need to call it on a class instance. Below are the examples to invoke class variable and instance variable. Class variable is not unique per instance of the class unlike instance variable.

car.class_var  # Output [] 
car._name      # Output 'abc' 
car._regd_num  # Output  'ACB123' 

Method invocation is also similar to variable invocation except the instance object is passed as the first argument of the function implicitly. Hence, below method calls are perfectly fine.

car.getName()

car.getRegdNumber()

Inheritance in Python

In simple terms, Inheritance means a subclass of an existing class. It allows code defined in one class to be reused in subclasses. Here subclass is called a child class and the class from which it is inherited is called parent class or base class. The sole purpose of inheritance is to increase code reusability.

Let us understand this in perspective to the Car class that we defined above. We created a simple and basic class called Car that has name and regd_num. There are many other things that defines state of a basic car but for simplicity, we only have these 2 attributes.

Now, we want to create a modern car by adding some modern equipments and accessories in the car. But it won't be a good idea to create another car that we have already created in above class Car. This will only add code duplication and bug to our new car if we start creating it again. Instead, we can reuse the existing code to create our modern car and extra functionalities and behavior to our modern car. in other words, we can use Inheritance to create out a modern car by inheriting from the already created car.

Below is the initial blueprint of our modern car.

from classexample import Car  


class ModernCar(Car): # Line 1 
    pass


modern_car = ModernCar("abc", "ACB123")
modern_car.getRegdNumber()

In Line 1, we just instructed Python runtime to consider this class(ModernCar) as a child class of class Car(parent). Now let us see what we have got in our ModernCar after execution. Notice, we have created an instance of ModernCar and calling getRegdNumber() method of parent class.

Output
Name of the car is 'abc' with registration number 'ACB123'

You can see now, with zero code duplication, we have our basic car created and we are ready to add modern equipments in ModernCar bu inheriting all the features of Car class. Now, let us understand what happened in the background.

In the background, when we instantiated the ModernCar class or the child class, Python looked for __init()__ in the child class and did not find it and then with the help of Method Resolution Order, it went to the parent class for search for the __init___() method and it found it there. Once found, it stopped at that level of hierarchy and initliased the parent class instance variable with the attributes that we supplied during ModernCar object initiliasation. The same process was followed when the method getRegdNumber() was called because we don't have any impelementation provided in our child class(ModernClass).

Now, what if Python runtime would have not found the method getRegdNumber() in Car class. It would look for this method in object class which is the parent class of all the other class. And after that exception would be thrown as we don't ve this method defined in object class.

Now let us add a power steering to our Modern Car. As power steering is mandatory to create our modern car, we need to accept this option during class initialisation and hence we require this paramter to be accepted in __init()__ method in ModernCar class. Below is the __init()__ method of ModernCar.

    def __init__(self, name, regd_num, power_steering):
        super().__init__(name, regd_num)
        self._power_steering = power_steering
        #Car.__init__(self, name, regd_num)
		
		
	    def getPowerSteering(self):
        print("Name of the car is '{}' with registration number '{}' and power steering '{}'".format(self._name, self._regd_num, self._power_steering ))
        return self._power_steering
		
modern_car = ModernCar("xyz", "XYZ123", "PS67")
modern_car.getPowerSteering()

To avoid the code duplicacy we simply called super() to initialize the parent class __init__() method. The first line of child constructor should be a call to super or self. We also added method in the child class that makes use of attributes of parent class as well as it's own attributes.

Method Overriding

Method overriding is a mechanism to add specific behavior to child class method that already exists in a parent class. For example, we already have getName() method defined in our parent class and now we want to modify the name to provide a specific prefix to the name of the car of ModernCar.

    def getName(self):
        return "SUP" + self._name
		

Till now, getName() method of parent class was getting invoked for modern_car.getName() but after the above implementation, the overriden method in child class(ModernCar) will be invoked.

Method overriding only makes sense in inheritance.

We use issubclass() method to check if a method is a dub class of another or not. Similarly, we can use isinstance() to check if two objects are instance of the same class.

As discussed above, polymorphism allows the usage of the same method name in different forms. Method overriding is a form of polymorphism. In above implementations, we have same method name (getName())implementation in both the chld and parent class. Python runtime decides which method to be invoked based on the instance on which the method is invoked.

Conclusion

In this article, we discussed about creating custom data type with class keyword in Python and it's usage with multiple examples. We discussed about constructors and __init__() method in detail. We also discussed about OOPS principle such as Inheritance and Polymorphsim.

About The Author

author-image

Further Reading on Python

1. Python Data Types

2. Different Python Operators

3. Python Flow Control

4. Python Functions

5. File Handling In Python