🔷 Introduction

In the beginner stage, you learned how to create classes and objects. Now it's time to go deeper and explore advanced Object-Oriented Programming concepts used in real-world applications.

This lesson will help you understand how to design scalable, reusable, and maintainable systems using OOP.

You will learn:

✔ Class vs instance attributes
✔ Class methods & static methods
✔ Inheritance (single & multiple)
✔ Method overriding
✔ Encapsulation (private attributes)
✔ Magic (dunder) methods

🟩 1. Class Attributes vs Instance Attributes

✅ Instance attributes

Defined inside __init__ and belong to each object:

class Car:
    def __init__(self, brand):
        self.brand = brand

✅ Class attributes

Shared by all objects:

class Car:
    wheels = 4  # class attribute

    def __init__(self, brand):
        self.brand = brand

Example:

c1 = Car("Toyota")
c2 = Car("BMW")

print(c1.wheels)  # 4
print(c2.wheels)  # 4

🟩 2. Class Methods & Static Methods

🔹 Class Method (@classmethod)

Works with the class, not the instance:

class Student:
    school = "MofidTech"

    @classmethod
    def change_school(cls, name):
        cls.school = name

🔹 Static Method (@staticmethod)

Independent utility function:

class Math:
    @staticmethod
    def add(a, b):
        return a + b

print(Math.add(2, 3))

🟩 3. Inheritance

Inheritance allows one class to reuse another.

🔹 Single Inheritance

class Animal:
    def speak(self):
        print("Animal sound")

class Dog(Animal):
    def bark(self):
        print("Woof")

🔹 Multiple Inheritance

class A:
    def method_a(self):
        print("A")

class B:
    def method_b(self):
        print("B")

class C(A, B):
    pass

🟩 4. Method Overriding

Child class modifies parent behavior:

class Animal:
    def speak(self):
        print("Animal sound")

class Dog(Animal):
    def speak(self):
        print("Woof")

🟩 5. Encapsulation (Private Attributes)

Use _ or __ to protect attributes:

class Bank:
    def __init__(self, balance):
        self.__balance = balance  # private

    def get_balance(self):
        return self.__balance

Access:

b = Bank(1000)
print(b.get_balance())

🟩 6. Magic (Dunder) Methods

Special methods that define behavior:

Example: __str__

class Person:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f"Person: {self.name}"

p = Person("Ali")
print(p)

Example: __len__

class MyList:
    def __init__(self, items):
        self.items = items

    def __len__(self):
        return len(self.items)

Example: Operator Overloading

class Number:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return self.value + other.value

🟧 7. Exercises (Hidden Solutions)

Exercise 1 — Class vs Instance Attribute

Create a class Book with:

Exercise 2 — Class Method

Create a class method that updates a class variable.

Exercise 3 — Inheritance

Create a class Vehicle and a subclass Car.

Exercise 4 — Encapsulation

Create a class with a private variable and getter.

Exercise 5 — Dunder Method

Create a class with __str__.

🟦 Conclusion

In this lesson, you explored advanced OOP concepts that are essential for real-world development:

These concepts are heavily used in frameworks like Django, Flask, and FastAPI.