Object-Oriented Programming
Classes and objects, the constructor and self, instance vs class variables, instance/class/static methods, encapsulation, all four types of inheritance, polymorphism and method overriding, abstraction with ABC, and magic methods.
Everything you've learned so far — variables, functions, data structures — works great for small programs. But as applications grow larger (think: a banking system, an e-commerce platform, a hospital management system), you need a way to model real-world things — like a "Student," a "Bank Account," or a "Product" — as organized units that bundle together their data and behavior. That's exactly what Object-Oriented Programming (OOP) is for.
This is one of the most important topics in this entire course — take your time with it.
1. What is Object-Oriented Programming?
What is it?
OOP is a programming style built around objects — units that combine data (attributes) and behavior (methods) together, modeled after real-world things.
Definition: Object-Oriented Programming is a programming paradigm that organizes code around objects, which bundle data (attributes) and behavior (methods) together.
Why do we use it?
- Real-world modeling — a "Student" object naturally has a name, age, and marks (data), and behaviors like
calculate_average()(methods). - Reusability — define a "template" (class) once, create as many objects from it as needed.
- Organization — large applications become far easier to manage when related data and logic are grouped together.
- Scalability — most professional, large-scale software (web frameworks, games, enterprise systems) is built using OOP principles.
How does it work?
OOP revolves around two core ideas:
- A class is a blueprint/template that defines what data and behavior objects of that type will have.
- An object is a specific instance created from that class, with its own actual data.
Think of a class like a cookie cutter, and objects like the individual cookies made using it — same shape (structure), different actual cookies (data).
2. Classes and Objects
Syntax
pythonclass ClassName: def __init__(self, parameters): self.attribute = value def method_name(self): # method body
Simple Example — A Student Class
pythonclass Student: def __init__(self, name, age, course): self.name = name self.age = age self.course = course def display_info(self): print(f"{self.name}, Age {self.age}, studying {self.course}") # Creating objects (instances) from the class student1 = Student("Aditi", 21, "Computer Science") student2 = Student("Rohan", 22, "Data Science") student1.display_info() student2.display_info()
Output:
Aditi, Age 21, studying Computer Science
Rohan, Age 22, studying Data ScienceExplanation of the Code
class Student:defines the blueprint.__init__is a special method (called the constructor) that runs automatically whenever a new object is created — it sets up the object's initial data.selfrefers to the specific object being created or used — it's how Python knows which student's name/age/course you're talking about.student1andstudent2are two separate objects, each with their own independent data, even though both come from the sameStudentclass.
Real-World Example
An e-commerce app might have a Product class, with each individual product (a specific shirt, a specific phone) as an object created from that class, each holding its own name, price, and stock quantity.
Common Mistakes
- Forgetting
selfas the first parameter in every method inside a class. - Forgetting parentheses when creating an object:
student1 = Student(this just refers to the class itself, not a new object). - Confusing the class (blueprint) with an object (an actual instance made from it).
Important Points
- A class is a blueprint; an object is an actual instance created from it.
- You can create as many objects as you want from a single class, each with independent data.
- Class names conventionally use
PascalCase(e.g.,Student,BankAccount), while objects/variables usesnake_case.
Practice
- Create a
Bookclass withtitle,author, andpriceattributes, and a method to display the book's details. Create two book objects and display both.
3. The Constructor (__init__) and self
What is it?
__init__ is a special method that runs automatically the moment a new object is created — it's where you set up the object's initial attribute values. self represents the specific object currently being worked with.
Why do we use it?
Without a constructor, you'd have to manually set every attribute after creating each object, one line at a time. The constructor does this setup automatically and consistently, every time.
Simple Example
pythonclass Employee: def __init__(self, name, salary): self.name = name self.salary = salary emp1 = Employee("Zara", 50000) print(emp1.name, emp1.salary) # Zara 50000
Explanation
- The moment
Employee("Zara", 50000)runs, Python automatically calls__init__(self, "Zara", 50000)behind the scenes. self.name = namestores the passed-in value as an attribute on that specific object — this is whyemp1.namegives back"Zara".
Why self Matters
pythonemp1 = Employee("Zara", 50000) emp2 = Employee("Karan", 60000) print(emp1.name) # Zara print(emp2.name) # Karan
self ensures that even though both objects were created from the same Employee class, each keeps its own independent data — changing emp1.salary never affects emp2.salary.
Common Mistakes
- Forgetting to include
selfas the first parameter of__init__(and every instance method) — this causes aTypeError. - Forgetting the
self.prefix when setting an attribute, e.g. writingname = nameinstead ofself.name = name— this creates a local variable that disappears immediately instead of actually storing data on the object.
Important Points
__init__runs automatically when an object is created — you never call it directly.selfmust be the first parameter of every regular (instance) method, though Python passes it automatically — you never pass it explicitly when calling the method.
4. Instance Variables vs Class Variables
What is it?
- An instance variable belongs to a specific object — each object has its own separate copy.
- A class variable is shared by all objects created from that class — there's only one copy, shared across every instance.
Simple Example
pythonclass Employee: company_name = "TechCorp" # class variable - shared by ALL employees def __init__(self, name, salary): self.name = name # instance variable - unique per employee self.salary = salary # instance variable - unique per employee emp1 = Employee("Zara", 50000) emp2 = Employee("Karan", 60000) print(emp1.company_name) # TechCorp print(emp2.company_name) # TechCorp print(emp1.name, emp2.name) # Zara Karan (different, as expected)
Explanation
company_nameis defined directly inside the class (not inside__init__), so it belongs to the class itself, shared identically by every employee object.nameandsalaryare set inside__init__usingself., making them unique per object.
Real-World Example
A company name, tax rate, or interest rate might reasonably be a class variable (the same for every object of that type), while a specific employee's name or a specific account's balance must be an instance variable (unique per object).
Common Mistakes
- Modifying a class variable through an instance in a way that accidentally creates a new instance variable instead of updating the shared value:
emp1.company_name = "NewCorp"creates an instance-level override just foremp1, it does not change the class variable for everyone.
Important Points
- Class variables are defined directly inside the class body, outside any method.
- Instance variables are typically defined inside
__init__usingself.. - Changing a class variable properly should be done via
ClassName.variable = new_value, not through an individual instance.
Practice
- Create a
Carclass with a class variablewheels = 4and instance variablesbrandandmodel. Create two car objects and print both their shared wheel count and their individual brands.
5. Instance Methods, Class Methods, and Static Methods
What is it?
Python supports three kinds of methods inside a class, each with a different purpose:
- Instance methods — the normal kind, operate on a specific object's data (use
self). - Class methods — operate on the class itself, not a specific object (use
cls, marked with@classmethod). - Static methods — don't need access to the object or the class at all; they're just related, grouped logically inside the class (marked with
@staticmethod).
Simple Example
pythonclass Employee: company_name = "TechCorp" def __init__(self, name, salary): self.name = name self.salary = salary def give_raise(self, amount): # instance method self.salary += amount @classmethod def change_company_name(cls, new_name): # class method cls.company_name = new_name @staticmethod def is_valid_salary(salary): # static method return salary > 0 emp1 = Employee("Zara", 50000) emp1.give_raise(5000) print(emp1.salary) # 55000 Employee.change_company_name("NewTechCorp") print(emp1.company_name) # NewTechCorp print(Employee.is_valid_salary(-500)) # False
Explanation of the Code
give_raise()is an instance method — it modifiesself.salary, meaning it works on one specific employee.change_company_name()is a class method — it modifiescls.company_name, affecting the shared class variable for all employees at once.is_valid_salary()is a static method — it doesn't needselforclsat all; it's just a related utility function that logically belongs with theEmployeeclass.
Comparison Table — Instance vs Class vs Static Methods
| Instance Method | Class Method | Static Method | |
|---|---|---|---|
| First parameter | self | cls | none (no automatic first param) |
| Decorator | (none) | @classmethod | @staticmethod |
| Operates on | A specific object's data | The class itself (shared data) | Neither — standalone logic |
| Called via | object.method() | Class.method() or object.method() | Class.method() or object.method() |
Common Mistakes
- Forgetting the
@classmethodor@staticmethoddecorator, which changes how Python passes arguments to the method — a very common source of confusing errors. - Using
selfinside a method meant to be a class method or static method.
Important Points
- Use instance methods for anything that needs a specific object's data.
- Use class methods when you need to affect or read data shared by the whole class.
- Use static methods for utility/helper functions that logically belong to the class but don't need object or class data.
Practice
- Add a class method to the
Employeeexample that creates a new employee from a string like"Zara-50000"(splitting on the hyphen).
6. Encapsulation
What is it?
Encapsulation means bundling data and the methods that operate on it together, while restricting direct outside access to some of that data — protecting an object's internal state from accidental or unauthorized changes.
Definition: Encapsulation is the practice of keeping an object's internal data protected, and only allowing controlled access through methods.
Access Levels in Python (By Convention)
Python doesn't enforce strict access control like some languages, but follows naming conventions:
| Prefix | Meaning | Example |
|---|---|---|
| No underscore | Public — accessible from anywhere | self.name |
Single underscore _ | Protected — internal use, but still accessible (a convention, not enforced) | self._balance |
Double underscore __ | Private — Python "name-mangles" it to discourage direct outside access | self.__pin |
Simple Example
pythonclass BankAccount: def __init__(self, owner, balance): self.owner = owner # public self.__balance = balance # private def deposit(self, amount): if amount > 0: self.__balance += amount def get_balance(self): return self.__balance account = BankAccount("Aditi", 1000) account.deposit(500) print(account.get_balance()) # 1500 # print(account.__balance) # AttributeError - can't access directly from outside
Explanation of the Code
self.__balanceis a private attribute — Python internally renames it (_BankAccount__balance) to discourage direct outside access.- Instead of letting outside code set
balancedirectly (which could allow invalid negative values), the class only allows controlled access throughdeposit()andget_balance().
Real-World Example
A bank account should never let external code set balance = -5000 directly — all changes must go through controlled methods like deposit() and withdraw(), which can enforce rules (e.g., "amount must be positive").
Common Mistakes
- Assuming double-underscore attributes are truly impossible to access from outside — they're just strongly discouraged and renamed, not fully unbreakable.
- Making everything private "for safety," even data that genuinely needs to be freely accessible — encapsulation should be applied thoughtfully, not everywhere.
Important Points
- Encapsulation protects internal data from unintended, invalid changes.
- Python relies on naming conventions (
_,__) rather than strict enforcement. - The
propertydecorator (introduced later in Intermediate Python) offers an even cleaner way to control attribute access.
Practice
- Create a
BankAccountclass with a private__balanceattribute, and methodsdeposit(),withdraw()(which should refuse to withdraw more than the balance), andget_balance().
7. Inheritance
What is it?
Inheritance lets one class (the child/subclass) reuse and extend the attributes and methods of another class (the parent/superclass) — modeling natural "is-a" relationships, like "a Car is a Vehicle."
Definition: Inheritance allows a class to acquire the properties and methods of another class, promoting code reuse.
7.1 Single Inheritance
pythonclass Vehicle: def __init__(self, brand): self.brand = brand def start(self): print(f"{self.brand} vehicle is starting") class Car(Vehicle): # Car inherits from Vehicle def __init__(self, brand, model): super().__init__(brand) # calls Vehicle's constructor self.model = model def display_info(self): print(f"{self.brand} {self.model}") car = Car("Toyota", "Corolla") car.start() # inherited from Vehicle car.display_info() # defined in Car
Output:
Toyota vehicle is starting
Toyota CorollaExplanation of the Code
class Car(Vehicle):meansCarinherits everything fromVehicle.super().__init__(brand)calls the parent class's constructor, soCardoesn't need to repeat the logic for settingself.brand— it reusesVehicle's existing setup.car.start()works even thoughstart()is only defined inVehicle— this is inheritance in action.
7.2 Multilevel Inheritance
A chain: grandparent → parent → child.
pythonclass Vehicle: def start(self): print("Vehicle starting") class Car(Vehicle): def drive(self): print("Car is driving") class SportsCar(Car): def turbo_boost(self): print("Turbo boost activated!") my_car = SportsCar() my_car.start() # from Vehicle my_car.drive() # from Car my_car.turbo_boost() # from SportsCar
7.3 Hierarchical Inheritance
Multiple child classes inherit from the same parent class.
pythonclass Vehicle: def start(self): print("Vehicle starting") class Car(Vehicle): pass class Motorcycle(Vehicle): pass car = Car() bike = Motorcycle() car.start() # Vehicle starting bike.start() # Vehicle starting
7.4 Multiple Inheritance
A single child class inherits from more than one parent class.
pythonclass Flyable: def fly(self): print("Flying") class Swimmable: def swim(self): print("Swimming") class FlyingBoat(Flyable, Swimmable): pass vehicle = FlyingBoat() vehicle.fly() # Flying vehicle.swim() # Swimming
Comparison Table — Types of Inheritance
| Type | Structure | Example |
|---|---|---|
| Single | One parent, one child | Vehicle → Car |
| Multilevel | Chain of inheritance | Vehicle → Car → SportsCar |
| Hierarchical | One parent, multiple children | Vehicle → Car, Vehicle → Motorcycle |
| Multiple | One child, multiple parents | FlyingBoat → Flyable, Swimmable |
Common Mistakes
- Forgetting to call
super().__init__(...)in the child's constructor, which means the parent's setup logic never runs. - Overusing multiple inheritance, which can make code harder to trace — Python resolves conflicts using something called the Method Resolution Order (MRO), which can get confusing if overused.
Important Points
super()gives access to the parent class's methods, most commonly its constructor.- A child class automatically has access to everything in its parent, and can also override or add new behavior.
Practice
- Create a
Personclass withnameandage, then aStudentclass that inherits from it and adds acourseattribute. - Create a
Birdclass with afly()method, and two child classesSparrowandPenguin— overridefly()inPenguinto print "Penguins can't fly" instead.
8. Polymorphism and Method Overriding
What is it?
Polymorphism means "many forms" — the same method name can behave differently depending on the object calling it. Method overriding is the most common way this happens: a child class redefines a method it inherited from its parent.
Definition: Polymorphism allows objects of different classes to be treated through a common interface, with each class providing its own specific behavior for shared method names.
Simple Example — Method Overriding
pythonclass Employee: def calculate_bonus(self): return 1000 class Manager(Employee): def calculate_bonus(self): # overrides the parent's version return 5000 employees = [Employee(), Manager(), Employee()] for emp in employees: print(emp.calculate_bonus())
Output:
1000
5000
1000Explanation of the Code
- Both
EmployeeandManagerhave a method calledcalculate_bonus(), butManagerprovides its own version, which overrides the parent's. - The
forloop calls.calculate_bonus()on each object without needing to know or check what specific type each one is — Python automatically runs the correct version for each object. This is polymorphism in action.
Real-World Example
An e-commerce app might have a base Product class with a calculate_shipping_cost() method, overridden differently by DigitalProduct (free/no shipping) versus PhysicalProduct (calculated based on weight).
Common Mistakes
- Confusing method overriding (redefining an inherited method) with overloading (defining a method multiple times with different parameters — which Python doesn't directly support the way some other languages do).
Important Points
- Method overriding lets a child class provide its own version of an inherited method.
- Polymorphism lets you write code that works generically across different object types, as long as they share the relevant method name.
Practice
- Create a base class
Shapewith a methodarea()returning0, and child classesCircleandRectanglethat overridearea()with the correct formulas.
9. Abstraction and Abstract Classes
What is it?
Abstraction means hiding complex internal implementation details and only exposing what's necessary — focusing on what something does, not how. In Python, this is often achieved using abstract classes, which define methods that must be implemented by any child class, without providing the actual implementation themselves.
Simple Example — Using the abc Module
pythonfrom abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass # no implementation here - child classes MUST provide one class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return 3.14159 * self.radius ** 2 class Rectangle(Shape): def __init__(self, length, width): self.length = length self.width = width def area(self): return self.length * self.width circle = Circle(5) rectangle = Rectangle(4, 6) print(circle.area()) # 78.53975 print(rectangle.area()) # 24
Explanation of the Code
Shape(ABC)marksShapeas an abstract base class — it cannot be instantiated directly:Shape()would raise aTypeError.@abstractmethodforces any child class (Circle,Rectangle) to provide its ownarea()method — if a child class forgets to implement it, Python raises an error when trying to create an object from that child class.- This guarantees a consistent interface: every
Shapesubclass is guaranteed to have a workingarea()method, even though each calculates it completely differently.
Real-World Example
A payment processing system might define an abstract PaymentMethod class with an abstract process_payment() method, ensuring every specific payment type (CreditCard, PayPal, BankTransfer) implements its own version, while the rest of the application can call .process_payment() generically on any of them.
Common Mistakes
- Trying to create an object directly from an abstract class:
Shape()→TypeError: Can't instantiate abstract class. - Forgetting to implement an abstract method in a child class, which also raises an error at instantiation time.
Important Points
- Abstract classes cannot be instantiated directly — they exist to be inherited from.
@abstractmethodforces child classes to implement specific methods, guaranteeing a consistent interface across all subclasses.- Requires importing
ABCandabstractmethodfrom the built-inabcmodule.
Practice
- Create an abstract class
PaymentMethodwith an abstract methodprocess_payment(amount), then implementCreditCardPaymentandUPIPaymentsubclasses.
10. Magic Methods (Dunder Methods)
What is it?
Magic methods (also called "dunder" methods, short for "double underscore") are special methods surrounded by double underscores (like __init__) that let your custom classes work naturally with Python's built-in syntax — like print(), +, ==, and len().
Common Magic Methods
| Method | Purpose | Triggered By |
|---|---|---|
__init__ | Constructor | Creating a new object |
__str__ | Human-readable string representation | print(obj), str(obj) |
__repr__ | Developer-facing representation | Typing the object name in a shell, debugging |
__len__ | Defines behavior for len() | len(obj) |
__eq__ | Defines behavior for == | obj1 == obj2 |
__add__ | Defines behavior for + | obj1 + obj2 |
Simple Example
pythonclass Product: def __init__(self, name, price): self.name = name self.price = price def __str__(self): return f"{self.name} - Rs.{self.price}" def __eq__(self, other): return self.price == other.price def __add__(self, other): return self.price + other.price item1 = Product("Shirt", 500) item2 = Product("Cap", 200) print(item1) # Shirt - Rs.500 (uses __str__ automatically) print(item1 == item2) # False (uses __eq__) print(item1 + item2) # 700 (uses __add__)
Explanation of the Code
- Without
__str__,print(item1)would show something unhelpful like<__main__.Product object at 0x...>. Defining__str__makes it print something meaningful instead. __eq__lets you customize what==actually means for your objects — here, two products are "equal" if their prices match, regardless of name.__add__lets the+operator work between twoProductobjects, even though Python has no built-in idea of what "adding two products" should mean — you define it yourself.
Real-World Example
Nearly every well-designed class in real applications defines at least __str__ (or __repr__) so that printing or debugging objects gives useful, readable output instead of a generic memory address.
Common Mistakes
- Forgetting that without
__str__/__repr__, printing an object gives an unhelpful default representation. - Defining
__eq__without considering what happens ifotherisn't even the same type — real-world code often adds type checks inside__eq__for safety.
Important Points
- Magic methods let custom objects integrate naturally with Python's built-in operators and functions.
__str__is for readable, user-facing output;__repr__is more for developers/debugging.
Practice
- Add a
__len__method to aCartclass (holding a list of items) so thatlen(cart)returns the number of items in it.
Common Beginner Mistakes — Summary for This Section
- Forgetting
selfas the first parameter of instance methods. - Confusing instance variables with class variables.
- Forgetting
super().__init__()in a child class's constructor. - Trying to instantiate an abstract class directly.
- Forgetting to define
__str__, resulting in unhelpful printed output for custom objects.
Cheat Sheet — OOP
pythonclass Animal: # class definition species_count = 0 # class variable def __init__(self, name): # constructor self.name = name # instance variable Animal.species_count += 1 def speak(self): # instance method print(f"{self.name} makes a sound") @classmethod def get_count(cls): # class method return cls.species_count @staticmethod def is_valid_name(name): # static method return len(name) > 0 class Dog(Animal): # inheritance def speak(self): # method overriding (polymorphism) print(f"{self.name} barks") from abc import ABC, abstractmethod class Shape(ABC): # abstraction @abstractmethod def area(self): pass
Mini Project: Bank Account Management System
Objective
Build a simple banking system using classes, demonstrating encapsulation, inheritance, and method overriding — a savings account and a current account, each with slightly different rules.
Requirements
- A base
Accountclass with deposit, withdraw, and balance-check functionality. - Balance must be kept private (encapsulated).
- A
SavingsAccountsubclass that adds interest. - A
CurrentAccountsubclass that allows a small overdraft.
Concepts Used
Classes, objects, constructors, encapsulation, inheritance, method overriding, magic methods.
Complete Code
pythonclass Account: def __init__(self, owner, balance=0): self.owner = owner self._balance = balance # protected: subclasses can still access it def deposit(self, amount): if amount <= 0: print("Deposit amount must be positive.") return self._balance += amount print(f"Deposited Rs.{amount}. New balance: Rs.{self._balance}") def withdraw(self, amount): if amount > self._balance: print("Insufficient balance.") return self._balance -= amount print(f"Withdrew Rs.{amount}. New balance: Rs.{self._balance}") def get_balance(self): return self._balance def __str__(self): return f"{self.owner}'s Account - Balance: Rs.{self._balance}" class SavingsAccount(Account): def __init__(self, owner, balance=0, interest_rate=0.05): super().__init__(owner, balance) self.interest_rate = interest_rate def add_interest(self): interest = self._balance * self.interest_rate self._balance += interest print(f"Interest added: Rs.{interest:.2f}. New balance: Rs.{self._balance:.2f}") class CurrentAccount(Account): def __init__(self, owner, balance=0, overdraft_limit=5000): super().__init__(owner, balance) self.overdraft_limit = overdraft_limit def withdraw(self, amount): # overriding to allow overdraft if amount > self._balance + self.overdraft_limit: print("Withdrawal exceeds overdraft limit.") return self._balance -= amount print(f"Withdrew Rs.{amount}. New balance: Rs.{self._balance}") savings = SavingsAccount("Aditi", 10000) savings.add_interest() savings.withdraw(2000) current = CurrentAccount("Rohan", 1000, overdraft_limit=3000) current.withdraw(3500) # allowed due to overdraft print(current)
Code Explanation
Accounthandles the shared logic — deposit, basic withdrawal, and balance checking — while keeping_balanceprotected.SavingsAccountextendsAccountwith anadd_interest()method, unique to savings accounts.CurrentAccountoverrideswithdraw()entirely, since its withdrawal rule (allowing overdraft) is fundamentally different from the base class's rule.__str__gives every account type (inherited fromAccount) a clean, readable printed format automatically.
Sample Output
Interest added: Rs.500.00. New balance: Rs.10500.00
Withdrew Rs.2000. New balance: Rs.8500.00
Withdrew Rs.3500. New balance: Rs.-2500
Rohan's Account - Balance: Rs.-2500Possible Improvements
- Add a transaction history list that records every deposit/withdrawal with a timestamp.
- Add a custom exception (from the Exception Handling file) instead of just printing error messages.
- Add a
transfer()method that moves money between two account objects.
Challenge Task
Add a FixedDepositAccount subclass that doesn't allow withdrawals at all until a certain "maturity" condition (e.g., a boolean flag) is met.
Interview Questions
Q1. What is the difference between a class and an object? Answer: A class is a blueprint defining attributes and behavior; an object is a specific instance created from that class, holding its own actual data.
Q2. What is the purpose of `self` in Python classes? Answer: self refers to the specific object a method is being called on, allowing the method to access and modify that particular object's own attributes.
Q3. What is the difference between instance variables and class variables? Answer: Instance variables are unique to each object (usually set in __init__ using self.); class variables are shared by every object of that class, defined directly in the class body.
Q4. What is the difference between a class method and a static method? Answer: A class method (marked @classmethod) receives the class itself (cls) and can access/modify class-level data. A static method (marked @staticmethod) receives neither self nor cls — it's just a related utility function grouped inside the class.
Q5. What is encapsulation, and how does Python implement it? Answer: Encapsulation is bundling data with the methods that operate on it, while restricting direct outside access. Python uses naming conventions — a single underscore (_var) signals "protected" (internal use), and a double underscore (__var) triggers name-mangling to discourage direct outside access.
Q6. What is the difference between method overriding and polymorphism? Answer: Method overriding is when a child class redefines a method inherited from its parent. Polymorphism is the broader concept — being able to call the same method name on different objects and get behavior appropriate to each object's actual class.
Q7. What is an abstract class, and why would you use one? Answer: An abstract class (built using ABC and @abstractmethod) cannot be instantiated directly and forces any subclass to implement specific methods, guaranteeing a consistent interface across all subclasses.
Q8. What is `__init__` vs `__str__`? Answer: __init__ is the constructor, called automatically when creating a new object, used to set initial attribute values. __str__ defines what print() or str() shows for that object — a human-readable representation.
Q9. What is the difference between single, multiple, multilevel, and hierarchical inheritance? Answer: Single: one parent, one child. Multiple: one child inherits from more than one parent. Multilevel: a chain (parent → child → grandchild). Hierarchical: multiple children inherit from the same single parent.
Practice Questions
Beginner
- Create a
Personclass withnameandageattributes and a method that prints a greeting. - Create a
Rectangleclass withlengthandwidth, and a method to calculate its area. - Create two objects from a
Carclass and print both their brand and model. - Add a class variable
bank_name = "ABC Bank"to anAccountclass and access it from two different objects. - Add a
__str__method to aBookclass so printing a book object shows its title and author neatly.
Intermediate
- Create a
Vehiclebase class and aCarsubclass that inherits from it and adds its own attribute. - Create an
Employeeclass and aManagersubclass that overrides acalculate_bonus()method. - Create a
BankAccountclass with a private__balanceattribute and controlleddeposit()/withdraw()methods. - Create an abstract
Shapeclass with an abstractarea()method, and implement it inCircleandSquaresubclasses. - Add
__eq__to aProductclass so two products are considered equal if their names and prices both match.
Challenge
- Design a small
Librarysystem with aBookclass and aMemberclass, where aMembercan borrow and return books (tracked as a list of borrowed books on the member object). - Build a
Shapehierarchy (Shape→Circle,Rectangle,Triangle) with an abstractarea()andperimeter()method, and write a function that accepts any shape and prints both values (demonstrating polymorphism). - Extend the Bank Account Management mini project to add a
TransactionHistoryclass that logs every deposit/withdrawal across all account types, and can print a full statement for any account.