Bytebook Compiler

Real Programs

Object-Oriented Python

Classes in Python: init and self, instance vs class attributes, methods, properties, inheritance with super(), polymorphism, and dunder methods like str and eq.

4 min read5 runnable examples4-question quiz

Everything in Python is an object — numbers, strings, functions, even classes. You define your own types with class.

Python
class Student:
    school = "Kalvium"                       # class attribute: shared by all students

    def __init__(self, name, roll):          # the constructor
        self.name = name                     # instance attributes: one per object
        self.roll = roll
        self.marks = []

    def add_mark(self, m):                   # a method: first parameter is always self
        self.marks.append(m)

    def average(self):
        return sum(self.marks) / len(self.marks) if self.marks else 0

s1 = Student("Asha", 101)
s2 = Student("Ravi", 102)
s1.add_mark(88); s1.add_mark(94)
s2.add_mark(76)
print(s1.name, s1.average(), s1.school)
print(s2.name, s2.average(), Student.school)
Output
Asha 91.0 Kalvium
Ravi 76.0 Kalvium

self is the object the method was called on — the same idea as this in C++, but written explicitly.

Encapsulation by convention

Python has no private keyword. By convention:

  • _name — "internal, please don't touch".
  • __name — name-mangled to _ClassName__name, which makes accidental access harder.

Properties let you add validation while keeping attribute syntax:

Python
class Account:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance

    @property
    def balance(self):                      # read with acc.balance
        return self._balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("deposit must be positive")
        self._balance += amount

acc = Account("Priya", 1000)
acc.deposit(500)
print(acc.balance)
try:
    acc.balance = 10**6                     # no setter defined: read-only
except AttributeError as e:
    print("blocked:", e)
Output
1500
blocked: property 'balance' of 'Account' object has no setter

Inheritance and super()

Python
class Person:
    def __init__(self, name, age):
        self.name, self.age = name, age
    def introduce(self):
        return f"I am {self.name}, {self.age}."

class Student(Person):
    def __init__(self, name, age, course):
        super().__init__(name, age)          # run the parent's constructor
        self.course = course
    def introduce(self):                     # override
        return super().introduce() + f" I study {self.course}."

class Teacher(Person):
    def introduce(self):
        return f"Good morning, I am Prof. {self.name}."

people = [Student("Meera", 19, "DSA"), Teacher("Rao", 45), Person("Kiran", 30)]
for p in people:
    print(p.introduce())                     # polymorphism: each object's own version
print(isinstance(people[0], Person), issubclass(Teacher, Person))
Output
I am Meera, 19. I study DSA.
Good morning, I am Prof. Rao.
I am Kiran, 30.
True True

Every method in Python behaves like a C++ virtual function: the call is resolved on the actual object at run time. Python also supports multiple inheritance (class C(A, B)), resolving names with the method resolution order (C.__mro__).

Dunder (magic) methods

Methods with double underscores let your objects work with Python's built-in syntax — this is Python's operator overloading:

Python
class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __str__(self):                       # print(v) and str(v)
        return f"({self.x}, {self.y})"
    def __repr__(self):                      # developer view, used in lists
        return f"Vector({self.x}, {self.y})"
    def __add__(self, other):                # v1 + v2
        return Vector(self.x + other.x, self.y + other.y)
    def __mul__(self, k):                    # v * 3
        return Vector(self.x * k, self.y * k)
    def __eq__(self, other):                 # v1 == v2
        return (self.x, self.y) == (other.x, other.y)
    def __len__(self):                       # len(v)
        return 2
    def __abs__(self):                       # abs(v): the length
        return (self.x ** 2 + self.y ** 2) ** 0.5

a, b = Vector(3, 4), Vector(1, 2)
print(a + b, a * 3, abs(a), a == Vector(3, 4), len(a))
print([a, b])
Output
(4, 6) (9, 12) 5.0 True 2
[Vector(3, 4), Vector(1, 2)]
Method Enables
__init__ construction: Vector(3, 4)
__str__, __repr__ print(obj), repr(obj)
__add__, __sub__, __mul__ +, -, *
__eq__, __lt__ ==, < (and sorting)
__len__, __getitem__ len(obj), obj[i]
__iter__, __next__ for x in obj
__call__ obj()

Class methods, static methods and dataclasses

Python
from dataclasses import dataclass

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius
    @classmethod
    def from_fahrenheit(cls, f):            # alternative constructor
        return cls((f - 32) * 5 / 9)
    @staticmethod
    def is_freezing(c):                     # utility: needs neither self nor cls
        return c <= 0

t = Temperature.from_fahrenheit(98.6)
print(round(t.celsius, 1), Temperature.is_freezing(-3))

@dataclass                                  # writes __init__, __repr__ and __eq__ for you
class Book:
    title: str
    author: str
    pages: int = 0

print(Book("Clean Code", "Robert Martin", 464))
Output
37.0 True
Book(title='Clean Code', author='Robert Martin', pages=464)

Test yourself

4 questions · pick an answer to check it instantly

  1. 1

    What is self in a method?

  2. 2

    Where should data shared by all objects of a class be stored?

  3. 3

    What does super().__init__(name) do in a child class?

  4. 4

    Which dunder method makes print(obj) show a friendly string?