Excellent.
DAY 18 is where Python’s Object-Oriented Programming model becomes crystal clear — not Java-style OOP, but Python’s dynamic, object-centric OOP.

Many people “use classes”.
Very few understand how Python actually resolves attributes and builds objects.


🧠 DAY 18 — OOP Fundamentals

(Classes, Instances & Attribute Lookup Model)


🔑 CORE IDEA OF DAY 18

In Python, classes are objects, instances are objects, and OOP is built on attribute lookup — not rigid blueprints.

Python OOP is dynamic, flexible, and runtime-driven.


1️⃣ Class vs Instance (NO CONFUSION AFTER TODAY)

class A:
    pass

a = A()
  • Aclass object
  • ainstance object
  • Both are objects in memory
type(A)   # type
type(a)   # A

🧠 Classes are created by type, just like instances are created by classes.


2️⃣ What Happens When You Create an Object

class A:
    def __init__(self, x):
        self.x = x

a = A(10)

Execution flow:

  1. A.__new__() → allocates memory
  2. A.__init__() → initializes object
  3. Reference assigned to a

⚠️ __init__ is NOT the constructor
It’s the initializer


3️⃣ Object Memory Model (CRITICAL)

Image
Image
Image
Image

Every instance contains:

instance
 ├── __dict__   → instance attributes
 └── __class__  → reference to class

Every class contains:

class
 ├── __dict__   → class attributes & methods
 └── __mro__    → method resolution order

4️⃣ Attribute Lookup Order (INTERVIEW GOLD)

When you access:

a.x

Python searches in this exact order:

  1. a.__dict__
  2. A.__dict__
  3. Parent classes (via MRO)
  4. object.__dict__

If not found → AttributeError

🧠 This explains shadowing, overriding, and bugs


5️⃣ Instance Attribute vs Class Attribute (TRAP)

class A:
    x = 10

a = A()
b = A()

a.x = 20

Results:

a.x → 20   (instance attribute)
b.x → 10   (class attribute)

Memory:

a.__dict__ = {'x': 20}
A.__dict__ = {'x': 10}

⚠️ Assignment creates instance attributes unless explicitly modifying class.


6️⃣ Why Mutable Class Attributes Are Dangerous 🔥

class A:
    data = []

a = A()
b = A()

a.data.append(1)
print(b.data)

[1]

Why?

  • data lives in class
  • Shared across all instances

🚨 This causes real production bugs

Correct pattern:

class A:
    def __init__(self):
        self.data = []

7️⃣ Methods Are Just Functions (BOUND vs UNBOUND)

class A:
    def f(self):
        pass

Access:

A.f        # function (unbound)
a.f        # method (bound)

What “bound” means:

  • self is automatically attached
a.f()  ≡  A.f(a)

🧠 Methods are descriptors — not special syntax.


8️⃣ self Is NOT a Keyword

class A:
    def f(this):
        print(this)

Works perfectly.

self is a convention, not a keyword.


9️⃣ Data Hiding in Python (Reality Check)

Python does NOT enforce private/protected.

class A:
    def __init__(self):
        self._x = 1
        self.__y = 2
  • _x → convention (“internal use”)
  • __y → name-mangled to _A__y

Not security. Just collision avoidance.


🔟 Why Python OOP Feels Different from Java

FeatureJavaPython
Access controlStrictConvention
Method bindingCompile-timeRuntime
InheritanceRigidFlexible
Duck typing

Python prioritizes behavior over hierarchy.


11️⃣ Composition vs Inheritance (Pythonic View)

Python prefers:

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

Over:

class Car(Engine):
    ...

Reason:

  • Fewer inheritance chains
  • Easier refactoring
  • Cleaner MRO

🔥 INTERVIEW TRAPS (DAY 18)

Q1

class A:
    x = 10

a = A()
print(a.x)
A.x = 20
print(a.x)

10
20


Q2

class A:
    pass

a = A()
print(hasattr(a, "__dict__"))

True


Q3

Why __init__ is not constructor?

✔ Object already exists
✔ It only initializes
✔ Allocation happens in __new__


🧠 DAY 18 MENTAL CHECKLIST

When debugging OOP:

  1. Where does attribute live?
  2. Is this instance or class attribute?
  3. Is method bound or unbound?
  4. Is shared state unintended?
  5. Is composition better here?

📝 DAY 18 ASSIGNMENT (MANDATORY)

1️⃣ Predict output (NO RUNNING):

class A:
    x = []

a = A()
b = A()
a.x.append(1)

print(b.x)

2️⃣ Explain clearly:

  • Attribute lookup order
  • Difference between class and instance attributes

3️⃣ Whiteboard task:

Draw:

  • Instance
  • Class
  • __dict__
  • Attribute resolution path

🔜 DAY 19 PREVIEW (🔥🔥🔥)

DAY 19 — OOP Internals: MRO, type, Metaclasses & __new__

You’ll learn:

  • How Python resolves multiple inheritance
  • What metaclasses really are
  • How classes are created
  • When __new__ matters

When ready, say 👉 “START DAY 19”