Skip to main content

Python OOP Explained: Every Core Concept with Examples

Python lets you write a working script without ever defining a class. So why bother with objects at all? Because the moment your program grows past a few hundred lines, you start passing the same clump of variables into every function — a user's name, email and permissions; a connection's host, port and timeout. Object-oriented programming is what you reach for when data and the behaviour that operates on it clearly belong together.

This guide walks through every core OOP concept in Python, with runnable examples.

Classes and Objects

A class is a blueprint. An object is a concrete thing built from that blueprint.

python
class Dog:
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed

    def bark(self):
        return f"{self.name} says woof!"

buddy = Dog("Buddy", "Beagle")
print(buddy.bark())   # Buddy says woof!

Two things confuse newcomers here.

__init__ is not a constructor in the C++ or Java sense — the object already exists by the time it runs. It is an initialiser that fills in the attributes. (The actual constructor is __new__, which you will rarely need to touch.)

self is just the instance, passed in explicitly. When you write buddy.bark(), Python translates it to Dog.bark(buddy). Python chose to make this visible rather than hide it behind an implicit this. The name self is a convention, not a keyword — but break it and every Python developer who reads your code will wince.

Attributes: Instance vs Class

Attributes defined inside __init__ belong to each instance. Attributes defined in the class body are shared by all instances.

python
class Employee:
    raise_percent = 1.04      # class attribute - shared
    count = 0

    def __init__(self, name, salary):
        self.name = name       # instance attributes - per object
        self.salary = salary
        Employee.count += 1

Lookup goes instance first, then class. That means emp.raise_percent reads the shared value, but emp.raise_percent = 1.06 creates a new instance attribute that shadows it, leaving everyone else untouched. To change it for everybody, assign to the class: Employee.raise_percent = 1.06.

Common trap
Never use a mutable object as a class attribute default. items = [] in the class body is shared by every instance ever created — one basket's groceries show up in all of them. Assign self.items = [] inside __init__ instead.

Methods: Instance, Class and Static

Python has three kinds of methods, distinguished by what gets passed as the first argument.

python
from datetime import date

class Employee:
    raise_percent = 1.04

    def __init__(self, name, salary):
        self.name, self.salary = name, salary

    def apply_raise(self):                     # instance method
        self.salary = round(self.salary * self.raise_percent)

    @classmethod
    def from_string(cls, text):                # class method
        name, salary = text.split("-")
        return cls(name, int(salary))

    @staticmethod
    def is_workday(day):                       # static method
        return day.weekday() < 5

An instance method receives the object as self. A class method receives the class as cls and is the idiomatic way to write alternative constructors — because cls respects subclasses, Manager.from_string(...) returns a Manager, not an Employee. A static method receives nothing special; it is a plain function parked inside the class because it belongs there conceptually.

Encapsulation

Encapsulation means bundling data with the methods that guard it, and keeping the internals private so you can change them later without breaking callers.

Python has no private keyword. It uses convention instead:

  • name — public, use freely.
  • _name — a single underscore signals “internal, don't touch.” Nothing enforces it. This is the famous we're all consenting adults here philosophy.
  • __name — a double underscore triggers name mangling: inside class Foo the attribute is stored as _Foo__name. That exists to prevent accidental clashes in subclasses, not to stop determined access.

The real tool for encapsulation is property, which lets you expose an attribute publicly while running code behind the scenes:

python
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius        # goes through the setter

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Below absolute zero")
        self._celsius = value

    @property
    def fahrenheit(self):             # computed, read-only
        return self._celsius * 9 / 5 + 32

t = Temperature(25)
print(t.fahrenheit)   # 77.0
t.celsius = -300      # ValueError
Why this matters
This is why Python developers don't write Java-style getters and setters up front. Start with a plain attribute; if you later need validation, convert it to a property and not one line of calling code has to change.

Inheritance

Inheritance lets a class reuse and extend another class.

python
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "..."

class Cat(Animal):
    def __init__(self, name, indoor=True):
        super().__init__(name)      # run the parent's initialiser
        self.indoor = indoor

    def speak(self):                # override
        return f"{self.name} says meow"

super() delegates to the next class in the lookup chain. Always prefer it over calling Animal.__init__(self, name) directly — with multiple inheritance, the hardcoded version silently skips classes.

Python supports multiple inheritance, and resolves ambiguity with the Method Resolution Order (MRO), computed by the C3 linearisation algorithm:

python
class A:
    def who(self): return "A"
class B(A):
    def who(self): return "B"
class C(A):
    def who(self): return "C"
class D(B, C):
    pass

print(D().who())        # B
print([cls.__name__ for cls in D.__mro__])
# ['D', 'B', 'C', 'A', 'object']

Depth is a liability. Most experienced Python code uses shallow hierarchies plus mixins — small classes that add one focused capability (JSONSerializableMixin, TimestampMixin) and are never instantiated on their own.

Polymorphism

Polymorphism means the same call works on different types. Python gets there three ways.

1. Method overriding

As above: speak() does something different for every subclass.

2. Duck typing

This is the Python-native flavour. There is no need for a shared base class — if an object has the method, it works:

python
for creature in [Cat("Momo"), Dog("Buddy"), Robot("R2")]:
    print(creature.speak())

None of these need to inherit from anything common. If it walks like a duck and quacks like a duck, it's a duck.

3. Operator overloading

Dunder (double-underscore) methods hook your class into Python's built-in syntax:

python
class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

    def __len__(self):
        return 2

v = Vector(1, 2) + Vector(3, 4)
print(v)              # Vector(4, 6)

Useful ones to know: __str__ (user-facing text), __repr__ (debug text), __len__, __getitem__, __iter__, __contains__, __call__, and __enter__ / __exit__ for context managers.

Gotcha
Defining __eq__ sets __hash__ to None, which makes your instances unhashable — they can no longer go in a set or be used as dict keys unless you define __hash__ too.

Abstraction

Abstraction means exposing what something does while hiding how. The abc module turns that into an enforced contract:

python
from abc import ABC, abstractmethod

class PaymentGateway(ABC):
    @abstractmethod
    def charge(self, amount):
        ...

    def receipt(self, amount):                 # shared concrete method
        return f"Charged {amount} via {type(self).__name__}"

class StripeGateway(PaymentGateway):
    def charge(self, amount):
        return True

PaymentGateway()      # TypeError: Can't instantiate abstract class

An abstract base class cannot be instantiated, and any subclass that fails to implement every abstract method cannot be instantiated either — so the error surfaces at object creation, not deep in production.

Composition Over Inheritance

Inheritance models “is a.” Composition models “has a,” and it is usually the better default:

python
class Engine:
    def start(self):
        return "Engine running"

class Car:
    def __init__(self):
        self.engine = Engine()      # a Car HAS an Engine

    def start(self):
        return self.engine.start()

A Car is not a kind of Engine. Composition keeps classes loosely coupled and swappable — you can hand Car a DieselEngine or a mock engine in tests without touching any hierarchy. Reach for inheritance only when the subclass genuinely substitutes for the parent everywhere the parent is used.

Less Boilerplate with Dataclasses

For classes that mostly hold data, dataclasses generates __init__, __repr__ and __eq__ for you:

python
from dataclasses import dataclass, field

@dataclass
class Point:
    x: float
    y: float
    tags: list[str] = field(default_factory=list)

p = Point(1.0, 2.0)
print(p)              # Point(x=1.0, y=2.0, tags=[])

Note field(default_factory=list) — that is the mutable-default trap from earlier, handled properly.

Wrapping Up

The four pillars — encapsulation, inheritance, polymorphism, abstraction — all show up in Python, but with a distinctly Python accent. Privacy is a convention backed by properties rather than a keyword. Polymorphism leans on duck typing more than on rigid class hierarchies. Abstraction is opt-in through abc.

The short version
Keep hierarchies shallow, prefer composition, expose plain attributes until you actually need a property, and let dataclasses handle the boilerplate. Classes are a tool for managing complexity — if a module of plain functions is clearer, use that instead.

Comments

Popular posts from this blog

An Overview on Data Science

So before we get into what is data science let us first understand what is data actually, and how it is important for business, e-Commerce, for security, for identity of someone, even for scientific purpose or research and for even much more. So data is nothing but a piece of information , the information that we are collecting could be anything it can be your date of birth, your body weight, your eyes or hair colour, your meal list, what you are searching in your mobile or computer, the places you visit, so we can say anything around you either connected to you or around you can be data.  But if someone is novice he will ask, how all these things can be data ? Answer is Data is everywhere but what type of data is our need and which type of data is not our need makes the all difference. Lets understand this clearly through an example- Suppose you want to do some shopping on Amazon, and you decided to buy a new mobile phone, you fixed the budget, then features that you want in the t...

All about data analysis and which programming language to choose to perform data analysis?

  What is data analysis ? Data analysis is the process of exploring, cleansing, transforming and modelling data in order to derive useful insight, supporting decision. Tools available for it ! There are two kinds of tools used in order to carry out data analysis: 1) Auto managed closed tools: These are the tools whose source code is not available, that is these are not open source. If you want to use these tools then you have to pay for them. Also, as these tools are not open source, if you want to learn these tools then you have to follow their documentation site. Though some auto managed tools have their free versions available.  Pros & Cons: Closed Source Expensive They are limited  Easy to learn Example: Tableau, Qlik View, Excel (Paid Version), Power BI (Paid Version), Zoho Analytics, SAS 2) Programming Languages: Then there are suitable programming languages which can derive the same result like auto managed closed tools.  Pros & Cons: These are open so...

Create your own QR code using python.

  How to crate your own QR code and embed any link in it ! First of all thanks guys if you are reading this blog, in this blog we will be discussing about how to create our own Quick Response (QR) code and to for this small project we will be using Python Programming language since my blog is all about python 😁.  Ok so before we dive into this project lets first understand a little about this QR code this thing what is this ? how it become so use full in modern world etc. etc. etc.   What is QR code ? A QR code is first invented by an Japanese automotive company named Denso Wave. After since it become so popular. Its because this image or in which it will be generated it can store a huge amount of data only in machine readable form. Its similarity matches to the barcode because both of them have black and white lines randomly in them. Using QR code we can track products, we can make easy payments, also can book our ticket online in one word it's safe to share inform...