loading...

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

arindamdawn profile image Arindam Dawn Updated on ・5 min read

Python is a multi-paradigm language. That's a cool-sounding term! 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, structure our code. Now, why is this important?
In real-world, while working on real-life projects, the problems we try to solve with programming are complex and involves 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.

print(type(2)) # <class 'int'>
print(type(2.5)) # <class 'float'>
print(type('Python')) # <class 'str'>
print(type(True)) # <class 'bool'>
print(type({})) # <class 'dict'>
print(type([])) # <class 'list'>
print(type(())) # <class 'tuple'>
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 behaviours 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.

class Avenger:
  def __init__(self, name):
    self.name = name

  def fight(self):
    print('👊')

spiderman = Avenger('Spiderman')

print(type(Avenger)) # <class 'type'>
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 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.

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')
thor = Avenger('Thor', 'thunder attack')

spiderman.fight() # dispatch a web
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.

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)
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.

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)
ronaldo = SoccerPlayer('ronaldo',22)
neymar = SoccerPlayer('neymar', 8)

max_goals = calculateMaxGoals(messi.goals, ronaldo.goals, neymar.goals)
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 details later but for now just roughly understood the concept of creating a class method.
class Calculator:
  def __init__(self,type):
    self.type = type

  @classmethod
  def calculate_sum(cls, num1, num2): 
        return num1 + num2
    # cls is just like self which needs to passed as 1st parameter

print(Calculator.calculate_sum(3,5)) # 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.
class Calculator:
  def __init__(self,type):
    self.type = type

  @staticmethod
  def multiply(num1, num2): 
        return num1 * num2
    # cls is just like self which needs to passed as 1st parameter

print(Calculator.multiply(3,5)) # 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!

Posted on by:

arindamdawn profile

Arindam Dawn

@arindamdawn

Software Engineer who loves building user interfaces. In the quest to learn, unlearn and re-learn things.

Discussion

pic
Editor guide
 

Hello. What is the difference between common function and classmethod?

def foo(self)

vs

@classmethod
def foo(cls)

?

When I should prefer one over another?

Thanks

 

Hey Peter,
Thanks for your question.

Well, a generic function is just a block of code that can be reused in your codebase. You can, for example, have a utility function dasherize which replaces spaces in words with a hyphen like this,

def dasherize(word):
  return word.replace(' ', '-')

print(dasherize("Hello Friend")) #Hello-Friend 

This you can reuse anywhere in your code.

Now class methods are a member of a class that you have defined. For example, you have a class called Vehicle and you have a class method named turnLeft().
You will be using this class method when you have to work with a vehicle object.

This is just a very basic explanation. There is no preference as such but depends on how you have structured your code and what you are trying to do.

Hope it helps!

 

Thank you

But I mean the difference between class method and class method with @classmethod

Oops, I probably misunderstood your question then!

Ok let me try to explain the difference:

Here is a class with a simple method

class Avenger:
  def __init__(self, name, weapon):
    self.name = name
    self.weapon = weapon

  def fight(self):
    print(self.weapon)

  @classmethod
  def showOrganization(cls):
    print('MARVEL INC')

spiderman = Avenger('Spiderman', 'dispatch a web')

spiderman.fight() # dispatch a web

Avenger.showOrganization() # this works
spiderman.showOrganization() # this also works

Here what I am trying to show is you can know the Avenger's organization name by simply calling the class method. You can also create an avenger and then get the name of the organization from that avenger but hope you understand the difference!

Great thank you for the example!

 

I think you meant to decorate the multiply method with @staticmethod in your final example. :)

 

Thanks Dan. I have updated it now :)

 

It is very clear to understand for beginners.
Thank You Dawn.

 

I'm glad that you found it useful :)

 

My God, I have been looking for a great explanation like this on Classes, properties. Thank you!

 

I am glad you found it useful 😊