Excellent.
DAY 17 removes all mystery around Python imports — one of the most common sources of production bugs, interview questions, and circular nightmares.

After today, you’ll know exactly how Python finds, loads, caches, and executes code.


🧠 DAY 17 — Modules, Imports & sys.path

(How Python Finds, Loads & Executes Code)


🔑 CORE IDEA OF DAY 17

import is not just “including a file”.
It is a controlled, cached execution process governed by sys.path.


1️⃣ What Is a Module vs Package (Precise)

Module

  • A single .py file
  • Has its own namespace
math.py

Package

  • A directory of modules
  • Contains __init__.py (explicit or implicit)
mypkg/
 ├── __init__.py
 ├── a.py
 └── b.py

Both are objects at runtime.


2️⃣ What REALLY Happens When You import x

Let’s break this down step by step.

import mymodule

Python does:

  1. Check module cache (sys.modules)
  2. If found → reuse it (no re-execution)
  3. If not found:
    • Search locations in sys.path
  4. Load module
  5. Execute module top to bottom
  6. Store module object in sys.modules
  7. Bind name mymodule in caller’s namespace

⚠️ Import executes code exactly once per process


3️⃣ The Import Search Path (sys.path)

Image
Image
Image
Image

Order of search:

  1. Current script directory
  2. Entries in PYTHONPATH
  3. Standard library directories
  4. Site-packages

Inspect:

import sys
sys.path

🧠 Wrong imports = wrong path order


4️⃣ sys.modules — The Import Cache (CRITICAL)

import math
import math
  • math loaded once
  • Second import reuses cached object
import sys
print("math" in sys.modules)

This explains:

  • Why imports are fast after first time
  • Why circular imports behave strangely

5️⃣ Import Styles (And What They REALLY Do)

import module

import math
math.sqrt(4)
  • Binds name math
  • Namespace is explicit
  • Safest form

from module import name

from math import sqrt
sqrt(4)
  • Binds sqrt directly
  • No module prefix
  • Faster lookup
  • Higher collision risk

from module import *

from math import *

Problems:

  • Pollutes namespace
  • Unclear origins
  • Breaks static analysis

🚨 Avoid in real code


6️⃣ Packages & __init__.py (Often Misunderstood)

__init__.py:

  • Executes when package is imported
  • Can expose API
  • Can import submodules
# mypkg/__init__.py
from .a import func

Now:

import mypkg
mypkg.func()

🧠 __init__.py defines the public surface of a package.


7️⃣ Absolute vs Relative Imports

Absolute (Recommended)

from mypkg.sub import mod

Relative (Inside packages only)

from . import mod
from ..utils import helper

Rules:

  • Relative imports only work inside packages
  • Never use relative imports in scripts

8️⃣ Circular Imports (🔥🔥🔥 Interview Favorite)

Example

# a.py
import b

# b.py
import a

What happens:

  • a starts importing
  • b imports a
  • a is only partially initialized
  • Attribute access fails

🚨 This causes AttributeError, not ImportError.


9️⃣ How to Break Circular Imports (REAL STRATEGIES)

✅ Strategy 1: Move import inside function

def f():
    import b

✅ Strategy 2: Refactor shared logic

  • Create common.py
  • Both modules import it

✅ Strategy 3: Import modules, not names

import module
module.func()

🔟 __name__ == "__main__" (DEMYSTIFIED)

Every module has __name__.

  • If run directly → "__main__"
  • If imported → module name
if __name__ == "__main__":
    main()

Purpose:

  • Allow file to be both script and module
  • Prevent accidental execution on import

This is not optional boilerplate — it’s a design pattern.


11️⃣ Import Time Side Effects (DANGEROUS)

# bad_module.py
print("hello")
x = expensive_call()

This runs on import, not on use.

Best practice:

  • Keep imports lightweight
  • Put heavy logic inside functions

12️⃣ Reloading Modules (Advanced)

import importlib
importlib.reload(module)

Use cases:

  • REPL
  • Debugging
  • Not recommended in production

Reloading does NOT:

  • Reset references held elsewhere
  • Fully clean state

🔥 INTERVIEW TRAPS (DAY 17)

Q1

import math
import math

How many times is math executed?
Once


Q2

Why circular imports cause partial objects?

✔ Module added to sys.modules before execution completes


Q3

Difference between import x and from x import y?

✔ Namespace binding
✔ Lookup path
✔ Collision risk


Q4

Why is if __name__ == "__main__" important?

✔ Prevent side effects
✔ Enable reuse
✔ Script/module duality


🧠 DAY 17 MENTAL CHECKLIST

When debugging imports:

  1. Is module already in sys.modules?
  2. Is execution happening at import time?
  3. Is path order correct?
  4. Is there a circular dependency?
  5. Is __init__.py doing too much?

📝 DAY 17 ASSIGNMENT (MANDATORY)

1️⃣ Predict behavior (NO RUNNING):

# a.py
print("A")
import b

# b.py
print("B")
import a

What prints? Why?


2️⃣ Explain clearly:

  • Why imports are cached
  • Why circular imports fail silently at first

3️⃣ Design question:

How would you structure a large project to avoid circular imports?


🔜 DAY 18 PREVIEW

DAY 18 — OOP Fundamentals (Python’s Object-Oriented Model)

You’ll learn:

  • Classes vs instances
  • Attribute lookup order
  • __init__ is not constructor
  • How Python OOP differs from Java/C++

When ready, say 👉 “START DAY 18”