# Object-Oriented Programming in Python — Classes, Objects, Inheritance

Source: https://skills101.in/learn/python/python-oop — Skills101 free tutorial (Python Programming). Updated 2026-11-09.

**Object-oriented programming (OOP)** groups data and the functions that work on it into one unit called a **class**. It's how large programs stay organised — and OOP questions appear in almost every fresher interview.

![A class Student is a blueprint with name, marks and grade(). Three objects — Asha 92, Ravi 74, Neha 85 — are made from it](https://skills101.in/learn-img/python/class-object.svg)

## 1. A first class

```python
class Student:
    def __init__(self, name, marks):
        self.name = name        # instance attributes
        self.marks = marks

    def grade(self):            # a method
        return "A" if self.marks >= 85 else "B"

asha = Student("Asha", 92)
ravi = Student("Ravi", 74)
print(asha.name, asha.grade())
print(ravi.name, ravi.grade())
```

```text
Asha A
Ravi B
```

- `class Student:` defines the blueprint.
- `__init__` runs automatically when you create an object (the **constructor**).
- `self` is the object itself. `self.name = name` stores data **on that object**.
- Methods are functions inside a class; their first parameter is `self`.

## 2. Class attributes vs instance attributes

```python
class Student:
    college = "Skills101"           # shared by ALL students
    count = 0

    def __init__(self, name):
        self.name = name            # different for each student
        Student.count += 1

a, b = Student("Asha"), Student("Ravi")
print(a.college, b.college, Student.count)
```

```text
Skills101 Skills101 2
```

## 3. Printing objects nicely: __str__

```python
class Book:
    def __init__(self, title, price):
        self.title, self.price = title, price

    def __str__(self):
        return f"{self.title} (₹{self.price})"

print(Book("Python Basics", 299))
```

```text
Python Basics (₹299)
```

Methods with double underscores (`__init__`, `__str__`, `__len__`, `__eq__`) are called **dunder** (magic) methods. Python calls them for you.

## 4. Inheritance

A child class gets everything from its parent class and can add or change things.

```python
class Employee:
    def __init__(self, name, salary):
        self.name, self.salary = name, salary

    def describe(self):
        return f"{self.name} earns ₹{self.salary}"

class Manager(Employee):
    def __init__(self, name, salary, team_size):
        super().__init__(name, salary)     # run the parent's __init__
        self.team_size = team_size

    def describe(self):                    # override
        return super().describe() + f" and manages {self.team_size} people"

print(Employee("Ravi", 40000).describe())
print(Manager("Neha", 90000, 6).describe())
```

```text
Ravi earns ₹40000
Neha earns ₹90000 and manages 6 people
```

## 5. The four pillars (interview favourite)

| Pillar | Meaning | In Python |
|---|---|---|
| **Encapsulation** | keep data and methods together; hide internals | attributes + methods in a class; `_name` means "internal" |
| **Abstraction** | show what it does, hide how | a simple method like `account.withdraw(500)` |
| **Inheritance** | reuse code from a parent class | `class Manager(Employee)` |
| **Polymorphism** | same method name, different behaviour | `describe()` in both classes |

The [OOP Concepts](https://skills101.in/learn/oops) subject explains these in depth, for every language.

### Encapsulation example

```python
class BankAccount:
    def __init__(self, balance):
        self._balance = balance           # "internal" by convention

    def withdraw(self, amount):
        if amount > self._balance:
            return "Insufficient balance"
        self._balance -= amount
        return f"Withdrawn ₹{amount}, left ₹{self._balance}"

acc = BankAccount(1000)
print(acc.withdraw(300))
print(acc.withdraw(5000))
```

```text
Withdrawn ₹300, left ₹700
Insufficient balance
```

## Common mistakes

- Forgetting `self` as the first parameter of a method.
- Writing `name = name` instead of `self.name = name` in `__init__`.
- Forgetting `super().__init__(…)` in a child's `__init__`, so the parent's attributes are missing.

## Quick recap

- Class = blueprint; object = an instance made from it.
- `__init__` sets up the object; `self` is the object.
- Class attributes are shared; instance attributes are per object.
- Inheritance with `class Child(Parent)` and `super()`.
- `__str__` controls how an object prints.


Practice quiz and free certificate: https://skills101.in/learn/python/python-oop