This posts is a complete guide to Python OOP (Object-Oriented Programming) β€” both basic and advanced topics, interview-relevant insights, code examples, and a data engineering mini-project using Python OOP + PySpark.


🐍 Python OOP: Classes and Objects (Complete Guide)


βœ… What is OOP?

Object-Oriented Programming is a paradigm that organizes code into objects, which are instances of classes.
It helps in creating modular, reusable, and structured code.


βœ… 1. Class and Object

πŸ”Ή Class: A blueprint for creating objects.

πŸ”Ή Object: An instance of a class with actual data.

class Car:
    def __init__(self, brand, year):
        self.brand = brand
        self.year = year

car1 = Car("Toyota", 2020)  # Object created
print(car1.brand)  # Output: Toyota

βœ… 2. __init__() Constructor

  • Special method that initializes an object
  • Called automatically when the object is created
def __init__(self, name):
    self.name = name

βœ… 3. Instance Variables and Methods

  • Belong to the object (not the class)
  • Accessed via self
class Person:
    def __init__(self, name):
        self.name = name  # instance variable

    def greet(self):  # instance method
        print(f"Hello, {self.name}")

βœ… 4. Class Variables and Methods

  • Shared across all instances
  • Use @classmethod
class Employee:
    count = 0  # class variable

    def __init__(self):
        Employee.count += 1

    @classmethod
    def total_employees(cls):
        return cls.count

βœ… 5. Static Methods

  • No access to self or cls
  • Utility function tied to class
class Math:
    @staticmethod
    def square(x):
        return x * x

βœ… 6. Encapsulation

  • Hide internal state using private/protected variables
class BankAccount:
    def __init__(self):
        self.__balance = 0  # private

    def deposit(self, amount):
        self.__balance += amount

    def get_balance(self):
        return self.__balance

βœ… 7. Inheritance

  • One class (child) can inherit properties and methods from another (parent). Encourages code reuse.
class Animal:
    def speak(self):
        print("Animal sound")

class Dog(Animal):
    def speak(self):
        print("Bark")

d = Dog()
d.speak()  # Output: Bark
class DataSource:
    def __init__(self, path):
        self.path = path

    def read(self):
        raise NotImplementedError

class CSVSource(DataSource):
    def read(self):
        print(f"Reading CSV from {self.path}")

class JSONSource(DataSource):
    def read(self):
        print(f"Reading JSON from {self.path}")

πŸ’Ό Use Case:

Define a DataSource base class for all sources (CSV, JSON, SQL, API) β†’ extend it in specific classes β†’ plug into ETL pipelines uniformly.


βœ… 8. Polymorphism

βœ… Concept:

Different classes implement the same method in different ways, and can be used interchangeably.

for animal in [Dog(), Animal()]:
    animal.speak()
sources = [CSVSource("data.csv"), JSONSource("data.json")]

for src in sources:
    src.read()  # Calls appropriate method based on object type

πŸ’Ό Use Case:

Let your pipeline loop over any data source without knowing whether it’s CSV, JSON, DB, or API β€” only requiring it to implement a .read() method.


βœ… 9. Abstraction (via abc module)

  • Hide implementation; expose only interface
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def area(self):
        return 3.14 * 5 * 5

βœ… 10. Special Methods / Dunder Methods

MethodPurpose
__init__Constructor
__str__String representation
__repr__Official string (for debugging)
__eq__Custom equality comparison
__len__Length of object
__getitem__Indexing support
__call__Callable object
class Book:
    def __init__(self, title): self.title = title
    def __str__(self): return f"Book: {self.title}"

print(Book("Python"))  # Book: Python

βœ… 11. Composition vs Inheritance

  • Inheritance: IS-A relationship
  • Composition: HAS-A relationship
class Engine:
    def start(self):
        print("Engine starts")

class Car:
    def __init__(self):
        self.engine = Engine()  # composition

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

βœ… Concept:

A class uses other classes inside it to build complex systems (not inheritance, but has-a).

class DataCleaner:
    def clean(self, df):
        return df.dropna()

class ETLJob:
    def __init__(self, reader, cleaner):
        self.reader = reader
        self.cleaner = cleaner

    def run(self):
        df = self.reader.read()
        return self.cleaner.clean(df)

πŸ’Ό Use Case:

An ETLJob uses a Reader and Cleaner β€” each component can be swapped (plug-and-play). Useful in modular data pipelines.


βœ… 12. Private (__var) vs Protected (_var)

PrefixMeaning
_varProtected (convention)
__varPrivate (name mangled)

βœ… 13. Property Decorator

Used to define getter/setter logic without changing attribute access.

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        self._celsius = (value - 32) * 5/9

βœ… 14. Dataclasses (Python 3.7+)

Auto-generates __init__, __repr__, __eq__

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

βœ… 15. Custom Exceptions

class MyError(Exception):
    pass

raise MyError("Something went wrong")

🧠 CHEAT SHEET (INLINE)

Class                β†’ Blueprint for object
Object               β†’ Instance of class
__init__()           β†’ Constructor method
self                 β†’ Refers to the current object
Instance Variable    β†’ Variable unique to object
Class Variable       β†’ Shared among all instances
@classmethod         β†’ Takes cls, not self
@staticmethod        β†’ No self or cls, utility function
Encapsulation        β†’ Hide data using __var or _var
Inheritance          β†’ Reuse parent class features
Polymorphism         β†’ Same interface, different behavior
Abstraction          β†’ Interface hiding via abc module
__str__, __repr__    β†’ Human/dev readable display
__call__             β†’ Make an object callable
@property               β†’ Turn method into attribute
@dataclass           β†’ Auto boilerplate class
Composition          β†’ Use other classes inside a class
Exception class      β†’ Custom error with `raise`

πŸ§ͺ Real Use Cases in Data Engineering

Use CaseOOP Use
Data Source abstraction (S3, HDFS)Base DataSource class with child implementations
FileParser classes (CSV, JSON, Parquet)Inheritance and polymorphism
Pipeline steps as reusable objectsComposition of objects (Extract, Transform, Load)
Metrics and Logging handlersSingleton or service classes
Configuration objects (per environment)Encapsulation of parameters

Let’s break down class methods, static methods, and dunder (magic) methods in Python β€” with clear πŸ”Ž concepts, βœ… use cases, and πŸ§ͺ examples.


βœ… 1. @classmethod

πŸ”Ž What:

  • Bound to the class, not instance
  • Takes cls as the first argument
  • Can access or modify class-level variables

βœ… Use Cases:

  • Alternate constructors
  • Tracking instances or metadata
  • Factory patterns

πŸ§ͺ Example:

class Book:
    count = 0  # class variable

    def __init__(self, title):
        self.title = title
        Book.count += 1

    @classmethod
    def get_count(cls):
        return cls.count

    @classmethod
    def from_string(cls, data):
        return cls(data.strip())

b1 = Book("Python")
b2 = Book.from_string("Data Science")
print(Book.get_count())  # Output: 2

βœ… 2. @staticmethod

πŸ”Ž What:

  • No access to self or cls
  • Pure utility function inside a class
  • Logical grouping of functionality

βœ… Use Cases:

  • Helper methods
  • Validators
  • Business logic without depending on state

πŸ§ͺ Example:

class MathTools:
    @staticmethod
    def square(n):
        return n * n

    @staticmethod
    def is_even(n):
        return n % 2 == 0

print(MathTools.square(4))  # 16
print(MathTools.is_even(5))  # False

βœ… 3. Dunder (Magic) Methods

πŸ”Ž What:

  • “Double underscore” methods: __init__, __str__, __len__, etc.
  • Python calls them automatically
  • Used to customize how your objects behave

βœ… Most Common Dunder Methods (with use cases):

MethodPurposeExample Use Case
__init__ConstructorCreate object with parameters
__str__String representationprint(obj) β†’ readable output
__repr__Debug representationrepr(obj) β†’ unambiguous
__len__Lengthlen(obj)
__eq__Equality check (==)Custom object comparison
__lt__, __gt__Comparisons (<, >)Sorting custom objects
__getitem__Indexingobj[0]
__setitem__Item assignmentobj[1] = value
__call__Make object callableobj()
__iter__, __next__IterationCustom iterable

πŸ§ͺ Dunder Method Examples:

class Book:
    def __init__(self, title, pages):
        self.title = title
        self.pages = pages

    def __str__(self):
        return f"Book: {self.title}"

    def __len__(self):
        return self.pages

b = Book("Python Basics", 300)
print(b)          # Book: Python Basics
print(len(b))     # 300
class Counter:
    def __init__(self):
        self.count = 0

    def __call__(self):
        self.count += 1
        return self.count

c = Counter()
print(c())  # 1
print(c())  # 2

🧠 Summary: When to Use What?

FeatureUse ForHas Access To
@classmethodAlternate constructors, class configcls
@staticmethodUtilities, logic helpersNone
Dunder methodsCustom object behavior (print, ==, etc.)Auto-called by Python

class Reader:
    def read(self):
        raise NotImplementedError

class CSVReader(Reader):
    def __init__(self, path):
        self.path = path

    def read(self):
        import pandas as pd
        print(f"Reading from {self.path}")
        return pd.read_csv(self.path)

class Transformer:
    def transform(self, df):
        raise NotImplementedError

class RemoveNulls(Transformer):
    def transform(self, df):
        return df.dropna()

class Writer:
    def write(self, df):
        print("Writing DataFrame")
        # In real scenario, df.to_csv(), to_sql(), etc.

class ETLJob:
    def __init__(self, reader, transformer, writer):
        self.reader = reader
        self.transformer = transformer
        self.writer = writer

    def run(self):
        df = self.reader.read()
        df = self.transformer.transform(df)
        self.writer.write(df)

# Instantiate and run
reader = CSVReader("sample.csv")
transformer = RemoveNulls()
writer = Writer()
job = ETLJob(reader, transformer, writer)
job.run()

Great question! These are core pillars of Object-Oriented Programming (OOP), and they each serve different purposes in code reuse, design flexibility, and extensibility.

Let’s break it down with simple definitions, examples, and comparisons. βœ…


πŸ”€ Polymorphism vs Inheritance vs Composition

ConceptDefinitionUse CaseRelationship Type
InheritanceOne class inherits attributes/methods from anotherCode reuse, subclassingIS-A
PolymorphismSame interface, different behavior depending on the objectInterchangeable behaviorsBehavior overloading
CompositionOne class has objects of other classesCombine functionality modularlyHAS-A

🧬 1. Inheritance

β€œIs-A” relationship β€” A subclass is a specialized version of a parent class.

βœ… Purpose:

  • Reuse and extend functionality
  • Share a common interface

πŸ§ͺ Example:

class Animal:
    def speak(self):
        print("Some sound")

class Dog(Animal):
    def speak(self):
        print("Bark")

d = Dog()
d.speak()  # Bark

πŸ”€ 2. Polymorphism

β€œMany forms” β€” Different classes implement the same method name, allowing flexible interface usage.

βœ… Purpose:

  • Swap objects without changing code
  • Method overriding or duck typing

πŸ§ͺ Example:

class Cat:
    def speak(self):
        print("Meow")

class Dog:
    def speak(self):
        print("Bark")

def animal_sound(animal):
    animal.speak()

animal_sound(Cat())  # Meow
animal_sound(Dog())  # Bark

βœ… This works due to polymorphism β€” same method name (speak) used differently across classes.


🧩 3. Composition

β€œHas-A” relationship β€” A class contains one or more objects of other classes.

βœ… Purpose:

  • Build complex behavior by combining simple objects
  • More flexible than inheritance
  • Avoid tight coupling

πŸ§ͺ Example:

class Engine:
    def start(self):
        print("Engine starts")

class Car:
    def __init__(self):
        self.engine = Engine()  # HAS-A engine

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

c = Car()
c.drive()  # Engine starts

🧠 Visual Comparison

Inheritance     β†’ Car IS-A Vehicle
Polymorphism    β†’ Car.drive(), Bike.drive() β€” same interface, different behavior
Composition     β†’ Car HAS-A Engine

🏁 Summary Table

FeatureInheritancePolymorphismComposition
RelationshipIS-ABehaves-likeHAS-A
Code reuseβœ… YesπŸ” Shared interfaceβœ… Yes
Flexibility❌ Rigidβœ… Highβœ… High
MaintenanceMay lead to tight couplingInterface-drivenLoosely coupled
Exampleclass Dog(Animal)animal.speak()Car has Engine

βœ… When to Use What?

If you want to…Use
Share base functionality across classesInheritance
Plug and play behaviors without caring about typePolymorphism
Build modular components that work togetherComposition


Pages: 1 2 3 4 5


Discover more from HintsToday

Subscribe to get the latest posts sent to your email.

Posted in

Leave a Reply

Your email address will not be published. Required fields are marked *

Discover more from HintsToday

Subscribe now to keep reading and get access to the full archive.

Continue reading