Import Mechanics
When you write import something, Python needs to find the file that contains something. This lesson explains where Python looks, what the mysterious if __name__ == '__main__' pattern means, and how to make your SmartNotes package runnable as a command.
This lesson covers sys.path (module search order), __name__ mechanics (why the guard clause works), relative vs absolute imports, and __main__.py for package entry points. Relative imports are mentioned briefly; absolute imports are recommended for clarity.
James creates the smartnotes/ package from Lesson 2. He adds a quick test at the bottom of models.py:
# models.py
from dataclasses import dataclass, field
@dataclass
class Note:
title: str
body: str
word_count: int
author: str = "Anonymous"
is_draft: bool = True
tags: list[str] = field(default_factory=list)
# Quick test
note = Note("Test", "Hello", 1)
print(f"Created: {note.title}")
He runs main.py, which imports Note from models.py. The terminal prints:
Created: Test
Python Tips
"Wait," James says. "I did not ask it to print 'Created: Test'. That came from models.py. Why did it run the test code?"
"Because Python runs the entire module when you import it," Emma says. "Every line, including that print at the bottom. That is why you need the __name__ guard."
The __name__ Variable
Every Python module has a built-in variable called __name__. Its value depends on how the module was loaded:
| How the file was run | Value of __name__ |
|---|---|
Directly: python models.py | "__main__" |
Imported: from models import Note | "models" |
Demonstrate this. Add a print to models.py:
print(f"__name__ is: {__name__}")
Run it directly:
uv run python smartnotes/models.py
Output:
__name__ is: __main__
Now run main.py, which imports models:
uv run python main.py
Output:
__name__ is: smartnotes.models
When you run a file directly, Python sets __name__ to "__main__". When a file is imported, __name__ is set to the module's full path (like "smartnotes.models").
The if __name__ == "__main__" Guard
Use this variable to protect code that should only run when the file is executed directly:
# models.py
from dataclasses import dataclass, field
@dataclass
class Note:
title: str
body: str
word_count: int
author: str = "Anonymous"
is_draft: bool = True
tags: list[str] = field(default_factory=list)
if __name__ == "__main__":
# This only runs when you execute: python models.py
# It does NOT run when another file imports Note
note = Note("Test", "Hello", 1)
print(f"Created: {note.title}")
Now when main.py imports Note, the test code does not run. But if you run models.py directly, it still works:
uv run python smartnotes/models.py
Output:
Created: Test
This pattern is used everywhere in Python. It separates "this file defines things" from "this file does things." The definitions are always available for import. The actions only happen when you run the file directly.
How Python Finds Modules
When you write from smartnotes.models import Note, Python searches for the module in a specific order:
- The current directory (where you ran the
pythoncommand) - Directories listed in the
PYTHONPATHenvironment variable - The standard library
- Installed packages (site-packages)
Why does this matter? If you ever see ModuleNotFoundError: No module named 'smartnotes', it means Python searched all four locations and could not find your module. The most common cause: you ran python from a different directory than your project. Knowing the search order helps you fix this.
You can see the full search path using the sys module. sys is a built-in Python module that gives you access to system-level information, including the list of directories Python searches for imports:
import sys
for path in sys.path:
print(path)
Output (example):
/home/james/project
/usr/lib/python3.12
/usr/lib/python3.12/lib-dynload
/home/james/.local/lib/python3.12/site-packages
The first entry is usually the current directory. That is why from smartnotes.models import Note works when you run Python from the project/ directory: Python finds smartnotes/ in the current directory, then models.py inside it.
If you run Python from a different directory, the import fails because smartnotes/ is not on the search path.
Absolute Imports Inside Packages
Inside the smartnotes/ package, modules import from each other. Always use the full package path:
# search.py (inside smartnotes/)
from smartnotes.models import Note
This is called an absolute import because it spells out the complete path: smartnotes.models. It is clear, unambiguous, and works everywhere.
You may see another style in other codebases called a relative import: from .models import Note. The dot means "from the current package." We will not use relative imports in this book. They are shorter to type but harder to understand, and they break if you try to run the file directly. Stick with absolute imports.
Making Your Package Runnable: __main__.py
You can run a module directly: python models.py. Can you run a package directly? Yes, with __main__.py.
Create smartnotes/__main__.py:
"""Entry point for running SmartNotes as a package."""
from smartnotes import Note, search_notes
def main() -> None:
"""Run a demo of SmartNotes."""
notes = [
Note("Python Tips", "Learn basics of coding", 4, "James", tags=["python"]),
Note("Debug Guide", "How to fix Python errors", 6, "James", tags=["python", "debug"]),
Note("Cooking Pasta", "Boil water and add salt", 6, "Emma", tags=["cooking"]),
]
print("=== SmartNotes ===")
print(f"Total notes: {len(notes)}")
results = search_notes(notes, "python")
print(f"Notes about Python: {len(results)}")
for note in results:
print(f" - {note.title} ({note.word_count} words)")
if __name__ == "__main__":
main()
Run the package:
uv run python -m smartnotes
Output:
=== SmartNotes ===
Total notes: 3
Notes about Python: 2
- Python Tips (4 words)
- Debug Guide (6 words)
The -m flag tells Python: "Run this package." Python looks for smartnotes/__main__.py and executes it. This is how professional Python tools work: python -m pytest, python -m pip, python -m venv.
The Complete Package Structure
After this lesson, your SmartNotes package looks like this:
project/
├── smartnotes/
│ ├── __init__.py # Public API: exports Note, search_notes, FileManager
│ ├── __main__.py # Entry point: python -m smartnotes
│ ├── models.py # Note dataclass
│ ├── search.py # search_notes, filter_by_tag
│ └── storage.py # FileManager class
├── tests/
│ ├── test_search.py
│ └── test_storage.py
└── main.py # Alternative entry point
| File | Purpose |
|---|---|
__init__.py | Defines public API, runs on import |
__main__.py | Runs when you execute python -m smartnotes |
models.py | Data structures |
search.py | Search operations |
storage.py | File I/O operations |
PRIMM-AI+ Practice: Predict name
Predict [AI-FREE]
Press Shift+Tab to enter Plan Mode.
You have this file:
# greetings.py
def hello(name: str) -> str:
return f"Hello, {name}!"
print(f"Module name: {__name__}")
if __name__ == "__main__":
print(hello("World"))
What is the output when you:
- Run
python greetings.pydirectly? - Run
python -c "from greetings import hello; print(hello('Alice'))"(importing it)?
Write both outputs.
Check your predictions
1. Running directly:
Module name: __main__
Hello, World!
2. Importing:
Module name: greetings
Hello, Alice!
When run directly, __name__ is "__main__", so both prints execute. When imported, __name__ is "greetings", so the guard clause blocks the second print. But the print(f"Module name: ...") runs in both cases because it is outside the guard.
Run
Press Shift+Tab to exit Plan Mode.
Create greetings.py and test both scenarios. Verify your predictions.
Investigate
Write one sentence explaining why the print(hello("World")) line ran when greetings.py was executed directly but not when it was imported. Use the __name__ variable in your explanation.
If you want to go deeper, run /investigate @greetings.py in Claude Code and ask: "Why does Python use the string __main__ instead of a boolean like is_main_module = True?"
Modify
Add a __main__.py to the smartnotes/ package that creates three sample notes, saves them to JSON using FileManager, and prints a summary. Run it with uv run python -m smartnotes.
Make [Mastery Gate]
Create a __main__.py for your SmartNotes package that:
- Creates 3 sample notes
- Saves them as JSON
- Loads them back
- Searches for a keyword
- Prints the results
Verify it works with uv run python -m smartnotes.
Try With AI
If Claude Code is not already running, open your terminal, navigate to your SmartNotes project folder, and type claude. If you need a refresher, Chapter 44 covers the setup.
Prompt 1: Debug an Import Error
I get this error when running my code:
ModuleNotFoundError: No module named 'smartnotes'
My directory structure is:
project/
├── smartnotes/
│ ├── __init__.py
│ └── models.py
└── main.py
I am running: python main.py from inside the project/ directory.
What could be wrong? Walk me through the debugging steps.
What you're learning: Import errors are the most common problem when organizing code into packages. The AI walks you through checking sys.path, verifying directory structure, and confirming __init__.py exists.
Prompt 2: Understand sys.path
Show me how to print sys.path and explain each entry.
If I install my package with pip, where does it go?
How is that different from running it from the project directory?
What you're learning: Understanding the module search path helps you debug import failures. The AI explains the difference between development (running from project dir) and installed packages.
Prompt 3: Entry Point Design
My __main__.py currently has hardcoded sample notes.
How should a real __main__.py work? Should it read
from a config file? Accept command-line arguments?
Show me a professional pattern.
What you're learning: Entry point design is a preview of Phase 7 (CLI tools). The AI shows patterns like argparse or typer that you will learn formally later, giving you context for what is coming.
James runs uv run python -m smartnotes. The demo prints three notes with a search result. He runs uv run python main.py. Same behavior, different entry point.
"Two ways to start the same program," he says. "Like the warehouse: you could enter through the loading dock or the front office. Different doors, same building."
"The difference matters for distribution," Emma says. "When you install a package with pip, python -m smartnotes works from anywhere on the system. python main.py only works from the project directory."
James nods. "What can go wrong with imports? You mentioned circular imports earlier."
"That is the most common import trap," Emma says. "Two modules that import each other. Python gets confused, and you get an error that makes no sense until you understand how imports actually work. That is Lesson 4."