30 Days Of Python 👨‍💻 - Day 8 - OOP Basics

This article is a part of a 30 day Python challenge series. You can find the links to all the previous posts of this series here
Python is a multi-paradigm language. Coming from the JavaScript universe, I am aware of this as JavaScript is a multi-paradigm language as well.
What it means is there is more than one explicit way of thinking about how we write our code in Python, and structure our code. Now, why is this important? In the  real-world, while working on real-life projects, the problems we try to solve with programming are complex and involve a lot of brainstorming even before writing a single line of code. Good programmers not only think about how to solve the problem with code but how to write code that is easier to maintain, easier to extend when necessary, and also easier to read and write. This way of structuring and organizing code is known as a programming paradigm. It’s like a pattern with some predefined set of rules which the developers can follow to avoid chaos. Imagine if every developer tried to be shrewd and wrote code in their unique way. Without a definite pattern, the project would be doomed!

Back to Python!

In Python, everything is an Object. The data types that I explored are all objects which have their own associated attributes and methods to do some action. These objects come from their classes as an instance. It means that all the data types in Python have a defined structure or prototype where all the details of their properties and functionalities have been defined.
  1. print(type(2)) # <class 'int'>  
  2. print(type(2.5)) # <class 'float'>  
  3. print(type('Python')) # <class 'str'>  
  4. print(type(True)) # <class 'bool'>  
  5. print(type({})) # <class 'dict'>  
  6. print(type([])) # <class 'list'>  
  7. print(type(())) # <class 'tuple'>  
  8. print(type(None)) # <class 'NoneType'>  
Just like the built-in classes, custom classes can be created to represent real-world things like Cars, Machines, Human Being, Animal or anything. This representation of real-world entities and their properties and behaviors into classes in code is can be thought of as a loose definition of Object Oriented Programming paradigm. Each class can then be used to created instances of an object. These objects can be combined with other objects to simulate real-world functionalities.
In the JavaScript universe as well, custom classes can be created (although classes in JS are more of syntactic sugar on top of prototype functions introduced with ES6). So in my mental model, I hypothetically connected them.
But to see OOP in action in Python will have to dive deep and write some code.
  1. class Avenger: def __init__(self, name): self.name = name def fight(self): print('👊') spiderman = Avenger('Spiderman'print(type(Avenger)) # <class 'type'>  
  2. print(type(spiderman)) # <class '__main__.Avenger'> --> instance of Avenger  
In Python, the naming convention for classes is camel-case and singular names, unlike variables which need to be snake-cased.
The __init__ is an initialization method (also called the constructor method). It is used to initialize the variables of the class. In the above class, name is being initialized. In JavaScript, for instance, this is done similarly in the constructor function of the class.
self is a keyword in Python which is a reference to the instance of the class. It is used to access the variables or attributes of the class. In my mental model, I compare this with the JavaScript this keyword.
In the Avenger class, fight is a method which is a hypothetical representation of what an Avenger will do when asked to fight. Here it just prints an emoji, but it can be any action. Using this Avenger class as a prototype, I created a Spiderman object. Similarly, this class can be used to create other avengers but it seems everyone would do the same thing on asking them to fight which is not cool.
  1. class Avenger: def __init__(self, name, weapon): self.name = name self.weapon = weapon def fight(self): print(self.weapon) spiderman = Avenger('Spiderman''dispatch a web')  
  2. thor = Avenger('Thor''thunder attack') spiderman.fight() # dispatch a web  
  3. thor.fight() # thunder attack  
Now that’s better. Each avenger performs something unique! This is a bare minimal skeleton of the class and it can be stuffed with a lot of functionalities to make the avengers more sophisticated.
The __init_which is called the constructor method gets called each time when the object is instantiated(created). It provides a lot of control mechanism as well such only allow object creation when a condition is met or add default values to the parameters.
  1. class MotorBike: def __init__(self, brand, age): if(age <= 15): self.brand = brand self.age = age def start(self): print(f'starting {self.brand}....') bullet = MotorBike('Royal Enfield Bullet',20)  
  2. bullet.start() # error. object is created only if age is less than or equals 15  

Coding Exercise

The task is to create a class SoccerPlayer with name and goals attributes, then create 3 player objects and then using a function find out the maximum goals and print that.
  1. class SoccerPlayer: def __init__(self, name, goals): self.name = name self.goals = goals def calculateMaxGoals(*args): print(args) return max(*args) messi = SoccerPlayer('messi'10)  
  2. ronaldo = SoccerPlayer('ronaldo',22)  
  3. neymar = SoccerPlayer('neymar'8) max_goals = calculateMaxGoals(messi.goals, ronaldo.goals, neymar.goals)  
  4. print(f'The highest number of goals is {max_goals} goals')  

@classmethod and @staticmethod

Methods can be attached to a class without creating an instance of it. There are two ways to do so.
@classmethod allows the creation of a method in the class by adding the so-called decorator @classmethod on top of the method name. I will explore decorators in detail later but for now just roughly understand the concept of creating a class method.
  1. class Calculator:  
  2.     def __init__(self, type):  
  3.         self.type = type  
  5.         @classmethod  
  6.         def calculate_sum(cls, num1, num2):  
  7.             return num1 + num2  # cls is just like self which needs to passed as 1st parameter  
  10. print(Calculator.calculate_sum(35))  # 8  
@staticmethod is very similar to the @classmethod. It just does not need to pass the cls keyword. This method can be called without instantiating the class.
  1. class Calculator:  
  2.     def __init__(self, type):  
  3.         self.type = type  
  5.         @staticmethod  
  6.         def multiply(num1, num2):  
  7.             return num1 * num2  # cls is just like self which needs to passed as 1st parameter  
  10. print(Calculator.multiply(35))  # 15  
That’s it for today. I will be covering and exploring the principles of object-oriented programming in detail and practise some exercises in the process of understanding the concepts. The mental model is being developed slowly and steadily to tackle more advanced topics in the upcoming days. Hope you are finding it interesting as well.
I am currently watching this interesting Q & A video with the founder of Python. Thought of sharing it as well :)

Python creator Guido van Rossum
Keep Coding. Let me know your thoughts.
Have a great one!