Python classes and objects

If you are new to python then I would advice you to read my Python basics article first and then proceed ahead. If you have some experience with python and want to explore classes and objects then you are in the right place, my friend.

We will start by understanding the basics of object-oriented programming. After that, we will learn what are classes and objects. How they are used in python. We look into the constructor, self and after that, we will look into the advantages of object-oriented programming.

Prerequisites:

  1. Basic understanding of syntax in Python.
  2. As Python2 will be deprecated by Jan 2020 we will use Python3. So Python3 must be configured on your local machine or you can use repl; It is an online text editor with python preconfigured.
  3. An IDE or text editor installed on the machine. I use Vscode you can use any another text editor if you want.

Understanding object oriented programming:

Python is a multi-paradigm programming language. It means python can use different approaches to solve the problem. One of the paradigms is procedural oriented programming. It structures the code like a recipe; set of steps in the form of function and code block. Another approach to solving the problem is by creating classes and objects. This is known as object-oriented oriented programming.

An object is a collection of data (variables) and methods that act on those data. And, the class is a blueprint for the object.

The important part to understand in object-oriented programming is that objects are at the center of the paradigm, not only representing the data but it also represents the structure of the program.

You can choose the paradigm that best suits the problem at hand, mix different paradigms in one program, and/or switch from one paradigm to another as your program evolves.

Advantages of object oriented programming:

  1. Inheritance: This is one of the most useful concepts in oops. It specifies that the child object will have all the properties and behavior of the parent object. Thus Inheritance allows us to define a class that inherits all the methods and properties from another class.

  2. Polymorphism: To understand the polymorphism let’s divide the word into 2 parts. The first part poly means many and morph means form, shape. Thus in simple one-word polymorphism means one task can be performed in many different ways. For example, you have a class animal, and all animals speak. But they speak differently. Here, the “speak” behavior is polymorphic and depends on the animal. So, the abstract “animal” concept does not actually “speak”, but specific animals (like dogs and cats) have a concrete implementation of the action “speak”. A polymorphism means same function name or method name being used for different types.

  3. Encapsulation: In object-oriented programming you can restrict access to methods and variables; we can make the methods and variables private. This can prevent the data from being modified by accident and is known as encapsulation. Python can’t make classes private like other programming languages.

Note: We will look into an example of each advantage when we clearly understand classes and objects.

Understanding Classes:

There are primitive data structures available in python for example numbers, strings, and lists that can be used for simple representation like name, place or cost, etc. But what if we have a condition where we have complex data. There is a pattern in the repetition of the properties of that data in that scenario what can we do? Suppose we have a data of 100 different animals. Every animal has a name, age, legs, etc. What if we want to add other properties to animal or one more animal gets added to that list. To manage all such complex scenario’s we need classes.

According to official python documentation, Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state.

Simplest Example of class is:

class ClassName:

    <statement-1>
    .
    .
    .
    <statement-N>

We use class keyword to define a class. We will define a class Car.

class Car:
    pass

In our example let’s create 2 methods. One is an engine and another is a wheel. These 2 methods define the parts available in our car. The below program will give us a better idea of classes:

class Car:

    def engine(self):
        print("I am an engine")

    def wheel(self):
        print("I am a wheel")

Thus to summarize class provides a blueprint of the what should be defined but it does not provide any real content. The Car class above defines the engine and wheel but it will not state what a specific car’s engine is or wheel is.

Understanding Objects:

If you are fully clearly with classes we will move to objects. If you still have any doubt feel free to ask doubt in the comment section below. Object is an instance of the class. Let’s consider the above example of a car. Here Car is our class and toyata is the object of the car. We can create multiple copies of the object. Every object must be defined using the class. The syntax for creating an object is:

toyata = Car()

Let’s consider our Car example for understanding the objects more better.

class Car:

    def engine(self):
        print("I am an engine")

    def wheel(self):
        print("I am a wheel")

toyota = Car()

The above toyota = Car() is a class object. Class objects support two kinds of operations: attribute references and instantiation. Class instantiation uses function notation. The instantiation operation (“calling” a class object) creates an empty object.

Now we can call different methods from our class Car using the object toyota that we have created. let’s call the method engine and wheel

class Car:

    def engine(self):
        print("I am an engine")

    def wheel(self):
        print("I am a wheel")

if __name__ == "__main__":
    toyota = Car()
    toyota.engine()
    toyota.wheel()

We will save the above code with a filename as mycar.py and now if we run this program then we will get the following output:

foo@bar:~$ python mycar.py
I am a engine
I am a wheel

The above method toyota.engine() is a method object. What exactly happens when a method is called? According to the python documentation with the help of above example toyota.engine() was called without an argument above, even though the function definition for engine() specified an argument. What happened to the argument? Surely Python raises an exception when a function that requires an argument is called without any — even if the argument isn’t actually used. The special thing about methods is that the instance object is passed as the first argument of the function. In our example, the call toyota.engine() is exactly equivalent to Car.engine(toyota). In general, calling a method with a list of n arguments is equivalent to calling the corresponding function with an argument list that is created by inserting the method’s instance object before the first argument.

Understanding constructor method:

The __init__ method is the constructor method in python. The constructor method is used to specify ( initialize ) the data.

Let us consider our car example:

class Car():

  def __init__(self): 
      print("Hello I am the constructor method.")

toyota = Car()

When we run our program we will get the following output:

foo@bar:~$ python mycar.py
Hello I am the constructor method.

Note: You will never have to call the init() method; it gets called automatically when you create a new Car instance.

Understanding self:

Now let’s understand what does self means and how we use it in object-oriented programming. The self represents the instance of a class. Using the self keyword we can access the attributes and methods of a class in python.

Now let us look at an example of how the self can be used. Let’s create a method named brand under our class Car. Inside that __init__ method or constructor method, we will pass a model by passing our car’s model name when we are instantiating our object. This name can be accessed anywhere in the class for example self.model in our case.

class Car(): 

  def __init__(self, model): 
    self.model = model
  		
  def brand(self): 
    print("The brand is", self.model)  

if __name__ == "__main__":
  toyota = Car("Innova")
  toyota.brand()

Now when we run our above program we will get the following output:

foo@bar:~$ python mycar.py
The brand is Innova

Let us consider now we have 2 brands; Toyota and Tata. In this scenario what can, we do is create another object using the same class. We can do this because both the parties here are producing the same entity.

class Car(): 

  def __init__(self, model): 
    self.model = model
  		
  def brand(self): 
    print("The brand is", self.model )  

if __name__ == "__main__":
  toyota = Car("Innova")
  tata = Car("harrier")
  toyota.brand()
  tata.brand()

Now when we run our above program we will get the following output:

foo@bar:~$ python mycar.py
The brand is Innova
The brand is harrier

Note: self is a convention and not a real python keyword. A self is a parameter in function and we can use another parameter name in place of it. But it is advisable to use self because it increases the readability of code.

Understanding instance attributes:

All the classes have objects and all the objects have attributes. Attributes are the properties. We use __init__() method to specify an object’s initial attribute.

Let’s consider our car example:

class Car():
    def __init__(self, model): 
        self.model = model  #instance attribute

In our example, each Car() has a specific model. Thus instance attributes are unique data to each instance.

Understanding class attributes:

We saw that instance attributes are specific to each object but class attributes are the same for all the instances. Let us look at the example of the car with the help of class attributes.

class Car():

    no_of_wheels = 4 #class attribute

    def __init__(self, model):
        self.model = model

So here each car can have different models but all the cars will have 4 wheels only.

Understanding inheritance:

When a class inherits the property of another class then this scenario is called inheritance. The class from which properties are inherited is called the base class. The class which inherits the property of other class is called derived class. Inheritance can be defined as a parent and child relationship. The child inherits the properties of the parent. Thus making the child a derived class while parent as a base class. Here term property refers to attributes and methods.

The syntax for a derived class definition looks like this:

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

It’s important to note that child classes override or extend the attributes and behaviors of parent classes. In other words, child classes inherit all of the parent’s attributes and behaviors but can also specify different behavior to follow. The most basic type of class is an object, which generally all other classes inherit as their parent.

Let’s modify our previous example to understand how inheritance works. We will create a base class named vehicle:

class Vehicle:
    def __init__(self, name):
        self.name = name
    
    def getName(self):
        return self.name

We have created a class Vehicle and instantiated a constructor with self.name which we have passed to getName method and returned from that method. Whenever this method will be called it will return the name that has been passed when an object is instantiated for that class.

Now let’s create a child class Car:

class Vehicle:
    def __init__(self, name):
        self.name = name
    
    def getName(self):
        return self.name

class Car(Vehicle):
  pass

As you can see from the above program our child class Car inherits all the method and attributes of parent class Vehicle. Now let’s use methods and attribute from the Vehicle class using our child class Car.

class Vehicle:

    def __init__(self, name, color='silver'):
        self.name = name
        self.color = color
    
    def get_name(self):
        return self.name
    
    def get_color(self):
        return self.color

class Car(Vehicle):
  pass

audi = Car("Audi r8")
print("The name of our car is", audi.get_name(), "and color is", audi.get_color())

When we run the above program we will get the following output:

foo@bar:~$ python mycar.py
The name of our car is Audi r8 and color is silver

We can also override a parent method or attribute. In the above example, we have defined our vehicle color has gray. But what if the color of our car is black? Now for every child class, we can’t make changes in the parent class. There comes the overriding functionality.

Let’s change our Audi r8 color to black.

class Vehicle:

    def __init__(self, name, color='silver'):
        self.name = name
        self.color = color
    
    def get_name(self):
        return self.name
    
    def get_color(self):
        return self.color

class Car(Vehicle):

    def get_color(self):
        self.color = 'black'
        return self.color

audi = Car("Audi r8")
print("The name of our car is", audi.get_name(), "and color is", audi.get_color())

As you can see in the above program, I have not instantiated a constructor. The reason behind this is that our child class Car is only using attributes from the Vehicle class and it is already inheriting them. So in such a scenario, there is no need to re-instantiate these attributes.

Now When we run the above program we will get the following output:

foo@bar:~$ python mycar.py
The name of our car is Audi r8 and color is black

Understanding super() in inheritance:

The super() alone returns a temporary object of the superclass that then allows us to call that superclass’s methods. Calling the previously built methods with super() saves us from needing to rewrite those methods in our subclass, and allows us to swap out superclasses with minimal code changes. Thus super extends the functionality of the inherited method.

Let’s extend our car example using super(). We will instantiate a constructor with brand_name and color in the parent class; Vehicle. Now we will call this constructor from our child class (Car) using super. We will create a get_description method which is returning self.model from Car class and self.brand_name, self.color from the Vehicle class.

class Vehicle:
 
    def __init__(self, brand_name, color):
        self.brand_name = brand_name
        self.color = color
 
    def get_brand_name(self):
        return self.brand_name
 
class Car(Vehicle):
 
    def __init__(self, brand_name, model, color):  
        super().__init__(brand_name, color)       
        self.model = model
 
    def get_description(self):
        return "Car Name: " + self.get_brand_name() + self.model + " Color:" + self.color
 
c = Car("Audi ",  "r8", " Red")
print("Car description:", c.get_description())
print("Brand name:", c.get_brand_name())

When we run the above program we get following output:

foo@bar:~$ python mycar.py
Car description: Car Name: Audi r8 Color: Red
Brand name: Audi

Understanding Mutliple Inheritance:

When a class inherits the method and attributes from multiple parent class then it is called multiple inheritance. This allows us to use the property from multiple base classes or parent classes in a derived or child class.

The general syntax of Multiple Inheritance is as follows:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

Let’s extend our vehicle example using multiple inheritance property. Here in this example, we will create 3 classes. Class Vehicle and Cost will be the Parent class. A Vehicle class represents the general property while the Cost class represents its pricing. As the car has a general property and costing both it will have 2 parent classes. Thus we will inherit multiple parent classes into our child class.

class Vehicle:

    def __init__(self, brand_name):
        self.brand_name = brand_name
    
    def get_brand_name(self):
        return self.brand_name


class Cost:		

    def __init__(self, cost):
        self.cost = cost
    
    def get_cost(self):
        return self.cost

 
class Car(Vehicle, Cost):	

    def __init__(self, brand_name, model, cost): 
        self.model = model 
        Vehicle.__init__(self, brand_name) 
        Cost.__init__(self, cost) 

    def get_description(self):
        return self.get_brand_name() + self.model + " is the car " + "and it's cost is " + self.get_cost()
		
c = Car("Audi ",  "r8", "2 cr")
print("Car description:", c.get_description())

Here you will find one difference in the above program than other programs from above. I have used Vehicle.__init__(self, brand_name) in the constructor of Car class. This is one way of calling attributes from the parent class. Another being super which I have explained above. When we run the above program we will get the following output:

foo@bar:~$ python mycar.py
Car description: Audi r8 is the car and it's cost is 2 cr

Though it can be used effectively, multiple inheritance should be done with care so that our programs do not become ambiguous and difficult for other programmers to understand.

Understanding Polymorphism:

The word polymorphism means having many forms. In programming, polymorphism means same function name (but different signatures) being uses for different types.

Let’s extend our car program using polymorphism. We will create 2 classes Car and Bike. Both the classes have common method or function but they are printing different data. The program is pretty much self-explanatory.

class Car: 

    def company(self): 
        print("Car belongs to Audi company.")
   
    def model(self): 
        print("The Model is R8.") 
   
    def color(self): 
        print("The color is silver.") 
   
class Bike: 

    def company(self): 
        print("Bike belongs to pulsar company.") 
   
    def model(self): 
        print("The Model is dominar.") 
   
    def color(self): 
        print("The color is black.") 
  
def func(obj): 
    obj.company() 
    obj.model() 
    obj.color() 
   
car = Car() 
bike = Bike() 
   
func(car) 
func(bike)

When we run the above code we will get the following output:

foo@bar:~$ python mycar.py
Car belongs to Audi company.
The Model is R8.
The color is silver.
Bike belongs to pulsar company.
The Model is dominar.
The color is black.

Understanding Encapsulation:

In most of the object-oriented programming, we can restrict access to methods and variables. This can prevent the data from being modified by accident and is known as encapsulation. This is also available in python.

Let’s integrate encapsulation into our car example. Now consider we have a super-secret engine. In the first example, we will hide our engine using a private variable. In the second example, we will hide our engine using a private method.

class Car:

  def __init__(self): 
    self.brand_name = 'Audi '
    self.model = 'r8'
    self.__engine = '5.2 L V10'
    
  def get_description(self):
        return self.brand_name + self.model + " is the car"
  
c = Car()
print(c.get_description)
print(c.__engine)

In this example self.__engine is a private attribute. When we run this program we will get the following output.

foo@bar:~$ python mycar.py
Audi r8 is the car
AttributeError: 'Car' object has no attribute '__engine'

We can also define a private method by adding __ in front of the method name. Following is the example of how we can define a private method.

class Car:

  def __init__(self):
      self.brand_name = 'Audi '
      self.model = 'r8'

  def __engine(self):
      return '5.2 L V10'

  def get_description(self):
      return self.brand_name + self.model + " is the car"
    
    
c = Car()
print(c.get_description())
print(c.__engine()) 

In this example def __engine(self) is a private method. When we run this program we will get the following output.

foo@bar:~$ python mycar.py
Audi r8 is the car
AttributeError: 'Car' object has no attribute '__engine'

Now suppose we want to access the private attribute or method we can do it in the following way:

class Car:

  def __init__(self):
      self.brand_name = 'Audi '
      self.model = 'r8'
      self.__engine_name = '5.2 L V10'

  def __engine(self):
      return '5.2 L V10'

  def get_description(self):
      return self.brand_name + self.model + " is the car"
    
    
c = Car()
print(c.get_description())
print("Accessing Private Method: ", c._Car__engine()) 
print("Accessing Private variable: ", c._Car__engine_name)

The output of following program is:

foo@bar:~$ python mycar.py
Audi r8 is the car
Accessing Private Method:  5.2 L V10
Accessing Private variable:  5.2 L V10

Other programming languages have protected class methods too, but Python does not. Encapsulation gives you more control over the degree of coupling in your code, it allows a class to change its implementation without affecting other parts of the code.

Small Recape of what we learn:

  1. We first got to know what is object-oriented programming.
  2. After that, we came across the advantages of object-oriented programming.
  3. We understood what are the classes.
  4. We learned how to use object and why are they so important in this paradigm.
  5. We learned what is the constructor method.
  6. What is self?
  7. We came across instance attributes and class attributes.
  8. After that we came to know what is an inheritance, How super is used in inheritance and multiple inheritance.
  9. We learned how polymorphism works.
  10. We learned how encapsulation works.

I hope I have made python classes and objects concepts clear. If not and you still have any doubts or any of the topic is not clear from the above feel free to comment in the comment section below.

The article is written with the help of official python documentation.

If you liked my work give it a heart or if you think I missed something mention it in the comment section. You can also add a suggestion for covering various future topics in python and frameworks related to the python.

Keep coding let’s be a Pythoner. ( Python + programmer 😜 )

My Twitter and Github Handle.