ASAmol Shukla
Projects
Courses
Prompts
Skills
Contact
Resume
Course Outline
Syllabus Overview

Complete Python Course: From Zero to Professional

Courses/Complete Python Course: From Zero to Professional/Lesson 16: Classes, Instances & Inheritance
50 mins lesson duration•10 mins read

Lesson 16: Classes, Instances & Inheritance

Object structure, __init__ constructor, instance parameters, methods, and parent-child overrides.

The OOP Paradigm

Object-Oriented Programming (OOP) is a design philosophy that groups related data (attributes) and behavior (methods) into cohesive packages called objects. Instead of scattering data and functions around your program, you model real-world things — a User, an Order, a Course — as objects where the data and its operations live together.

Before OOP, programs were a pile of variables and functions: user_name, user_age, print_user(user). OOP bundles them: a User object holds its own data and knows how to act. This bundling — called encapsulation — keeps programs organized as they grow from 100 lines to 100,000 lines.

What You'll Learn in This Lesson

  • Define classes and create instances
  • Initialize objects with __init__
  • Write methods and understand self
  • Inherit from parent classes and override methods

Class vs Instance

Term Meaning Analogy
Class The blueprint / template A cookie cutter
Instance A concrete object made from the class One cookie stamped out
class Person:
    def __init__(self, name, role):
        self.name = name
        self.role = role

    def get_profile(self):
        return f"Name: {self.name}, Role: {self.role}"

user = Person("Rahul", "Student")   # user is an INSTANCE
print(user.get_profile())           # Name: Rahul, Role: Student

Mental model: the class is the blueprint — it never exists as a physical thing. The instance is what you actually use. You can stamp out unlimited instances from one blueprint, each with its own data.


init — The Constructor

__init__ is the initializer: it runs automatically the moment you create an instance. It receives the creation arguments and stores them on the object:

class Person:
    def __init__(self, name, role):   # runs on Person(...)
        self.name = name              # store ON the object
        self.role = role

The self parameter is the object itself — it is passed automatically, so you never pass it explicitly.

Mental model: self.name = name means "attach a label called name to THIS object, pointing at the value". Each instance gets its own independent labels.

What if you skip __init__? Instances still work, but every attribute must be set manually after creation:

class Empty:
    pass

e = Empty()
e.name = "Amol"   # works, but clunky — attributes appear ad-hoc

The __init__ method guarantees every instance is born fully formed with the right attributes — the hallmark of reliable OOP.


Methods — Functions Attached to Objects

A method is a function defined inside a class. Its first parameter is always self:

class Dog:
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed

    def bark(self):
        return "Woof!"

    def describe(self):
        return f"{self.name} is a {self.breed}"

Call a method on an instance with the dot: dog.bark(). Python automatically passes dog as self.

Why self is passed automatically: dog.bark() is syntactic sugar for Dog.bark(dog). Python injects the receiver as the first argument — that's why every method declares self first, even when it takes no other arguments.

Class attributes vs instance attributes:

Kind Defined Shared? Example
Class attribute Inside class body Shared by all instances species = "Canis"
Instance attribute In __init__ via self Unique per instance self.name
class Dog:
    species = "Canis familiaris"   # class attribute — shared

    def __init__(self, name):
        self.name = name            # instance attribute — unique

d1, d2 = Dog("Rocky"), Dog("Bella")
print(d1.species, d2.species)   # same for both
print(d1.name, d2.name)         # different for each

Inheritance — Child Classes Reuse Parent Code

A child class inherits all attributes and methods from its parent, then adds or overrides what it needs. This expresses "is-a" relationships: an Instructor is a Person.

class Instructor(Person):          # (Person) = inherit
    def __init__(self, name, department, course):
        super().__init__(name, role="Instructor")   # call parent's __init__
        self.department = department
        self.course = course

    def get_profile(self):         # OVERRIDE the parent method
        parent = super().get_profile()
        return f"{parent} | Dept: {self.department} | Course: {self.course}"

Key points:

  • super() gives access to the parent class — super().__init__(...) reuses the parent's setup without repeating code.
  • Overriding means redefining a parent method in the child; the child's version wins for child instances.
  • A child gets everything the parent has — attributes, methods, even the parent's parent.

Mental model: inheritance is a family tree. The child is born with all the parent's traits and can add its own or change inherited ones. super() is the "call mom" button — reuse the parent's implementation instead of rewriting it.


Why OOP?

Benefit Explanation
Cohesion Data + behavior live together
Reuse Inherit instead of copy-paste
Modeling Code mirrors the real world
Maintainability Change one class, not every call site

When should you NOT use OOP? For tiny scripts, plain functions are simpler and perfectly fine. OOP earns its keep when you have many objects sharing structure and behavior — user accounts, products, database models, UI components. The rule: functions first; classes when you see duplication across related data.


Common Mistakes to Avoid

  • Mistake: Forgetting self as the first parameter — Fix: every instance method needs self first.
  • Mistake: Defining a class but never creating an instance — Fix: obj = MyClass(...) actually runs the code.
  • Mistake: Repeating parent setup instead of super().__init__(...) — Fix: call super and add only what's new.
  • Mistake: Naming classes in snake_case — Fix: classes use PascalCase (BankAccount), functions/variables use snake_case.
  • Mistake: Putting default attributes at the class level when they should be per-instance — Fix: mutable defaults belong in __init__ via self.

Professional Tips & Tricks

  • super().__init__(...) keeps the parent setup without repeating code.
  • Name classes in PascalCase (BankAccount) and methods/attributes in snake_case.
  • Give every class a docstring describing its responsibility.
  • Initialize all attributes in __init__ — objects should be born complete.
  • Use class attributes for shared constants; instance attributes for per-object data.

Key Takeaways

  • A class is a blueprint; an instance is a concrete object.
  • __init__ initializes every new instance.
  • self is the object itself, passed automatically.
  • Inheritance reuses parent code; super() reaches the parent.
  • Overriding lets a child redefine a parent method.
  • Class attributes are shared; instance attributes are per-object.

Next up: Inheritance & polymorphism in depth.

Interactive Lesson Code Snippet
# Class constructor and Inheritance demo
class Person:
    def __init__(self, name, role):
        self.name = name
        self.role = role

    def get_profile(self):
        return f"Name: {self.name}, Role: {self.role}"

# Child inherits from Person parent class
class Instructor(Person):
    def __init__(self, name, department, course):
        # Initialize parent attributes
        super().__init__(name, role="Instructor")
        self.department = department
        self.course = course

    # Override get_profile method
    def get_profile(self):
        parent_details = super().get_profile()
        return f"{parent_details} | Dept: {self.department} | Course: {self.course}"

# Instantiate objects
user = Person("Rahul", "Student")
teacher = Instructor("Amol Shukla", "AI Engineering", "Data Science")

print(user.get_profile())
print(teacher.get_profile())
Language: python

Lesson Code (Python)

# Class constructor and Inheritance demo
class Person:
    def __init__(self, name, role):
        self.name = name
        self.role = role

    def get_profile(self):
        return f"Name: {self.name}, Role: {self.role}"

# Child inherits from Person parent class
class Instructor(Person):
    def __init__(self, name, department, course):
        # Initialize parent attributes
        super().__init__(name, role="Instructor")
        self.department = department
        self.course = course

    # Override get_profile method
    def get_profile(self):
        parent_details = super().get_profile()
        return f"{parent_details} | Dept: {self.department} | Course: {self.course}"

# Instantiate objects
user = Person("Rahul", "Student")
teacher = Instructor("Amol Shukla", "AI Engineering", "Data Science")

print(user.get_profile())
print(teacher.get_profile())

Console Output

Name: Rahul, Role: Student
Name: Amol Shukla, Role: Instructor | Dept: AI Engineering | Course: Data Science

Code Visualization Tips

  • 🧠Picture the class as a cookie cutter and instances as the cookies it stamps out.
  • 🧠Draw one box per instance with its own attribute values — each box is independent.
  • 🧠Visualize inheritance as a family tree: the child inherits the parent's traits and adds its own.

Professional Tips & Tricks

  • ⚡super().__init__(...) keeps the parent setup without repeating code.
  • ⚡Name classes in PascalCase (BankAccount) and methods/attributes in snake_case.
  • ⚡Give every class a docstring describing its responsibility.

Python Code Judge & Practice Arena

LeetCode Style

Run real Python 3.12 WebAssembly code directly in your browser against automated test suites.

Solved:0 / 20
0 / 370 XP
Challenges:
Problem 1 of 20

Problem 1: BankAccount Class 1

Easy+10 XP
Create a class `BankAccount_1` with `__init__(self, owner, balance=0)` and methods `deposit(amount)` and `withdraw(amount)` (returns False if insufficient funds, True otherwise).
Sample Test Cases:
Input: (lambda b: (b.deposit(50), b.balance))(BankAccount_1('Amol', 100))
Expected: (150, 150)
Input: (lambda b: (b.withdraw(40), b.balance))(BankAccount_1('Amol', 100))
Expected: (True, 60)
main.pyPython 3.12 (WASM)
1
2
3
4
5
6
7
8
9
10
11
12
Press Run Code to test or Submit to verify test cases

Test Your Knowledge

Instant feedback

Quick Check: Lesson 16: Classes, Objects, `__init__`, `self` & Instance State

1 / 20
What is the purpose of the `__init__` method in a Python class? class User: def __init__(self, name): self.name = name

Up next · Continue learning

Inheritance & Polymorphism

Deep-dive into inheritance, method overriding, polymorphism, isinstance, and the MRO.

9 mins read50 mins
Start next lesson
Previous: Modules, Packages & ImportsNext: Inheritance & Polymorphism
Made withbyAmol Shukla·amolshukla.online
ASAmol Shukla

AI Developer, Trainer & Agentic AI Expert building practical learning systems and real-world AI applications.

Explore

  • Projects
  • Courses
  • Prompts
  • Skills
  • Contact
  • Experience
  • Blogs

Connect

  • Resume
  • Contact
© 2026 Amol Shukla·Created withbyamolshukla.online
Back to top