› Module 8: Object-Oriented Programming › 8.1 Classes and Objects
M O D U L E

Module 8: Object-Oriented Programming

Modeling data and behavior together using classes, plus Python's own conventions for privacy and flexibility.

8.1 Classes and Objects

A class is defined with the class keyword and typically includes an __init__ method — Python's constructor, called automatically the moment you create a new instance with ClassName(...).

Every instance method's first parameter is, by strong convention, named self — an explicit reference to the specific object the method was called on. This is a notable contrast with Java or C++, which hide this reference behind an implicit this; Python always makes it visible and explicit.

A Simple Class (from the Guide)

# A SIMPLE CLASS
class Car:
    def __init__(self, color):
        self.color = color # instance attribute

    def drive(self):
        print(f"{self.color} car driving")

c1 = Car("red")
c1.drive() # "red car driving"
class Car (blueprint) c1 = Car("red") color = "red" c2 = Car("blue") color = "blue" self refers to whichever instance is being operated on
Figure 8.1 — A class blueprint produces independent object instances, each with its own attribute values.

8.2 Inheritance

A class inherits from another by naming it in parentheses: class Dog(Animal):.

The child class ("subclass") automatically receives every attribute and method the parent ("base class") defines, and it can selectively override any of them by simply redefining a method with the same name.

Inside an overriding method, calling super().method(...) reaches back and invokes the parent's original version — letting you extend inherited behavior instead of throwing it away completely.

class Animal class Dog(Animal) class Cat(Animal)
Figure 8.2 — A derived class inherits from, and can override, its base class.
# INHERITANCE IN ACTION
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "Some sound"

class Dog(Animal):
    def speak(self): # Overriding speak
        return f"{self.name} says Woof!"

d = Dog("Buddy")
print(d.speak()) # "Buddy says Woof!"

8.3 Encapsulation Conventions

Python has no true "private" keyword the way Java does. Instead, privacy is a matter of convention rather than strict compiler enforcement.

The Single Underscore (_name)

A single leading underscore (_name) is a polite signal to other developers meaning "this is for internal use — please don't touch it from outside the class," but the language does not actually stop anyone from accessing it.

The Double Underscore (__name) & Name Mangling

A double leading underscore (__name) triggers something stronger called name mangling, where Python internally renames the attribute to _ClassName__name.

This makes accidental external access, or accidental overriding by a subclass, considerably less likely — though, notably, still not impossible for someone determined enough.

# Encapsulation and Name Mangling
class BankAccount:
    def __init__(self, balance):
        self._protected_balance = balance # polite convention
        self.__secret_pin = 1234          # name-mangled

acc = BankAccount(5000)
print(acc._protected_balance) # 5000 (accessible, but frowned upon)

# print(acc.__secret_pin)     # Raises AttributeError!
print(acc._BankAccount__secret_pin) # 1234 (reveals the mangled name!)

Pythonic Getters & Setters with @property

class Student:
    def __init__(self, marks):
        self._marks = marks

    @property
    def marks(self):
        return self._marks

    @marks.setter
    def marks(self, val):
        if val < 0 or val > 100:
            raise ValueError("Marks must be between 0 and 100")
        self._marks = val

8.4 Polymorphism & Magic (Dunder) Methods

Python largely achieves polymorphism through "duck typing" — summarized by the old saying:

"If it walks like a duck and quacks like a duck, treat it like a duck."

In practice, this means Python doesn't check an object's exact class before calling a method on it; it only cares whether the object actually has that method.

Dunder (Double Underscore) Magic Methods

Separately, dunder ("double underscore") methods such as __init__, __str__, __len__, and __add__ let your own custom classes hook directly into Python's built-in syntax.

Defining __add__ on a class, for example, lets you use the ordinary + operator on your own objects, and __str__ controls exactly what print() displays for an instance of that class.

# Operator Overloading with Dunder Methods
class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __add__(self, other): # Overloads the + operator!
        return Vector(self.x + other.x, self.y + other.y)

    def __str__(self):        # Controls print(v) representation!
        return f"Vector({self.x}, {self.y})"

    def __len__(self):        # Enables len(v)
        return int((self.x**2 + self.y**2)**0.5)

v1 = Vector(2, 3)
v2 = Vector(4, 5)
v3 = v1 + v2
print(v3) # "Vector(6, 8)"
MODULE COMPREHENSIVE QUIZ

Module 8 Final Quiz

Test your understanding across all 4 topics of this module with interview-grade questions, tricky edge cases, and code output predictions.

Take Module 8 Final Quiz →
Yukti Mascot