FroquizFroquiz
HomeQuizzesSenior ChallengeGet CertifiedBlogAbout
Sign InStart Quiz
Sign InStart Quiz
Froquiz

The most comprehensive quiz platform for software engineers. Test yourself with 10000+ questions and advance your career.

LinkedIn

Platform

  • Start Quizzes
  • Topics
  • Blog
  • My Profile
  • Sign In

About

  • About Us
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Β© 2026 Froquiz. All rights reserved.Built with passion for technology
Blog & Articles

Python OOP Guide: Classes, Inheritance, Magic Methods and Design Patterns

Master object-oriented programming in Python. Covers classes, inheritance, multiple inheritance, dunder methods, properties, class methods, dataclasses, and OOP design patterns.

Yusuf SeyitoğluMarch 11, 20262 views10 min read

Python OOP Guide: Classes, Inheritance, Magic Methods and Design Patterns

Python supports multiple programming paradigms, but object-oriented programming is central to writing clean, reusable, and maintainable Python code. Whether you are building a web framework, a data pipeline, or a CLI tool, understanding Python OOP deeply sets you apart.

Classes and Instances

python
class Dog: # Class variable -- shared across all instances species = "Canis familiaris" def __init__(self, name, age): # Instance variables -- unique to each instance self.name = name self.age = age def bark(self): return f"{self.name} says: Woof!" def __repr__(self): return f"Dog(name={self.name!r}, age={self.age!r})" def __str__(self): return f"{self.name}, {self.age} years old" dog1 = Dog("Rex", 3) dog2 = Dog("Bella", 5) print(dog1.bark()) # Rex says: Woof! print(dog1.species) # Canis familiaris print(Dog.species) # same -- class variable print(repr(dog1)) # Dog(name='Rex', age=3) print(str(dog1)) # Rex, 3 years old

Magic Methods (Dunder Methods)

Methods with double underscores on both sides are called dunder (double underscore) methods. They define how objects behave with Python's built-in operations.

python
class Vector: def __init__(self, x, y): self.x = x self.y = 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 __mul__(self, scalar): return Vector(self.x * scalar, self.y * scalar) def __eq__(self, other): return self.x == other.x and self.y == other.y def __len__(self): return 2 def __getitem__(self, index): return (self.x, self.y)[index] def __abs__(self): return (self.x ** 2 + self.y ** 2) ** 0.5 v1 = Vector(1, 2) v2 = Vector(3, 4) print(v1 + v2) # Vector(4, 6) print(v1 * 3) # Vector(3, 6) print(v1 == v1) # True print(len(v1)) # 2 print(v1[0]) # 1 print(abs(v2)) # 5.0

Key dunder methods

MethodTriggered by
__init__obj = Class()
__repr__repr(obj), REPL display
__str__str(obj), print(obj)
__len__len(obj)
__eq__obj == other
__lt__obj < other
__add__obj + other
__getitem__obj[key]
__contains__item in obj
__iter__for item in obj
__enter__, __exit__with obj:

Properties

Use @property to control attribute access with getter/setter logic:

python
class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("Temperature below absolute zero") self._celsius = value @property def fahrenheit(self): return self._celsius * 9/5 + 32 @fahrenheit.setter def fahrenheit(self, value): self.celsius = (value - 32) * 5/9 t = Temperature(25) print(t.celsius) # 25 print(t.fahrenheit) # 77.0 t.fahrenheit = 32 print(t.celsius) # 0.0 t.celsius = -300 # ValueError: Temperature below absolute zero

Class Methods and Static Methods

python
class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day @classmethod def from_string(cls, date_string): year, month, day = map(int, date_string.split("-")) return cls(year, month, day) @classmethod def today(cls): import datetime d = datetime.date.today() return cls(d.year, d.month, d.day) @staticmethod def is_valid(year, month, day): return 1 <= month <= 12 and 1 <= day <= 31 def __repr__(self): return f"Date({self.year}, {self.month}, {self.day})" d = Date.from_string("2025-03-11") # classmethod factory today = Date.today() print(Date.is_valid(2025, 13, 1)) # False -- staticmethod
  • @classmethod receives cls (the class) as first argument β€” used for factory methods and alternative constructors
  • @staticmethod receives nothing extra β€” just a function namespaced inside the class

Inheritance

python
class Animal: def __init__(self, name): self.name = name def speak(self): raise NotImplementedError("Subclass must implement speak()") def __repr__(self): return f"{self.__class__.__name__}({self.name!r})" class Dog(Animal): def speak(self): return f"{self.name}: Woof!" class Cat(Animal): def speak(self): return f"{self.name}: Meow!" class Duck(Animal): def speak(self): return f"{self.name}: Quack!" animals = [Dog("Rex"), Cat("Whiskers"), Duck("Donald")] for animal in animals: print(animal.speak())

super()

Call the parent class's method:

python
class Vehicle: def __init__(self, make, model): self.make = make self.model = model def describe(self): return f"{self.make} {self.model}" class ElectricCar(Vehicle): def __init__(self, make, model, battery_kwh): super().__init__(make, model) # call parent __init__ self.battery_kwh = battery_kwh def describe(self): base = super().describe() # call parent describe() return f"{base} (Electric, {self.battery_kwh}kWh)" car = ElectricCar("Tesla", "Model 3", 82) print(car.describe()) # Tesla Model 3 (Electric, 82kWh)

Multiple Inheritance and MRO

Python supports multiple inheritance. The Method Resolution Order (MRO) determines which method is called when the same name exists in multiple bases:

python
class A: def hello(self): return "A" class B(A): def hello(self): return "B" class C(A): def hello(self): return "C" class D(B, C): pass d = D() print(d.hello()) # "B" -- follows MRO: D -> B -> C -> A print(D.__mro__) # (D, B, C, A, object)

Python uses C3 linearization to compute MRO. You can always inspect it with ClassName.__mro__.

Abstract Classes

Use abc.ABC to define abstract base classes that enforce method implementation:

python
from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self) -> float: pass @abstractmethod def perimeter(self) -> float: pass def describe(self): return f"Area: {self.area():.2f}, Perimeter: {self.perimeter():.2f}" class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): import math return math.pi * self.radius ** 2 def perimeter(self): import math return 2 * math.pi * self.radius shape = Shape() # TypeError: Can't instantiate abstract class circle = Circle(5) print(circle.describe()) # Area: 78.54, Perimeter: 31.42

Dataclasses

Python 3.7+ dataclasses reduce boilerplate for data-holding classes:

python
from dataclasses import dataclass, field @dataclass class Product: name: str price: float category: str = "general" tags: list = field(default_factory=list) def discounted_price(self, discount_pct): return self.price * (1 - discount_pct / 100) p = Product("Laptop", 999.99, "electronics", ["tech", "portable"]) print(p) # Product(name='Laptop', price=999.99, ...) print(p == Product("Laptop", 999.99, "electronics", ["tech", "portable"])) # True @dataclass(frozen=True) # immutable class Point: x: float y: float

Dataclasses auto-generate __init__, __repr__, and __eq__. Add frozen=True for immutability, order=True for comparison operators.

Common Interview Questions

Q: What is the difference between __str__ and __repr__?

__repr__ should return an unambiguous representation useful for debugging β€” ideally something you could paste into the interpreter to recreate the object. __str__ should return a human-readable string. print() uses __str__; the REPL and repr() use __repr__. If only __repr__ is defined, it is used as fallback for __str__.

Q: What is the difference between a classmethod and a staticmethod?

A classmethod receives the class as its first argument (cls) and can access or modify class state. A staticmethod receives no implicit first argument β€” it is just a regular function that lives in the class namespace for organizational purposes.

Q: What does super() do in Python?

super() returns a proxy object that delegates method calls to the next class in the MRO. It is most commonly used to call the parent class's __init__ or an overridden method while still executing the subclass version.

Practice Python on Froquiz

OOP is tested in Python interviews at every level. Test your Python knowledge on Froquiz β€” covering OOP, decorators, comprehensions, async, and more.

Summary

  • __init__ initializes instances; class variables are shared, instance variables are unique
  • Dunder methods define behavior for built-in operations (+, len, in, with, etc.)
  • @property adds getter/setter logic to attribute access
  • @classmethod receives cls β€” use for factory methods; @staticmethod is a plain namespaced function
  • super() delegates to the next class in the MRO
  • Multiple inheritance uses C3 linearization β€” inspect with ClassName.__mro__
  • abc.ABC and @abstractmethod enforce interface contracts
  • @dataclass eliminates boilerplate for data-holding classes

About Author

Yusuf Seyitoğlu

Author β†’

Other Posts

  • CSS Advanced Techniques: Custom Properties, Container Queries, Grid Masonry and Modern LayoutsMar 12
  • GraphQL Schema Design: Types, Resolvers, Mutations and Best PracticesMar 12
  • System Design Fundamentals: Scalability, Load Balancing, Caching and DatabasesMar 12
All Blogs