Skip to content
Hello Python
5/7

Modules & Packages

Topic 5 of 7, with 3 concept checks. Organizing code across files with import, __name__, and __init__.py

Turn Python files into stable import boundaries

Import boundaries

Understand how modules are located and initialized, what a package exports, and why import direction and executable module guards matter to reusable interview code.

Core lesson 01

__name__ is '__main__' only when a file is run directly (python file.py), and the module's own name when imported elsewhere — the guard lets a file provide both reusable functions and a runnable script.

Python sets a special variable __name__ in every module. When you run a file directly, Python sets that file's __name__ to '__main__'. When the same file is imported from elsewhere, __name__ is set to the module's actual name instead. Wrapping script-only code (a demo, a CLI entry point) in if __name__ == "__main__": means that code only runs when the file is executed directly, not when another file imports functions from it.

Python example
# mathutils.py
def add(a, b):
    return a + b

if __name__ == "__main__":
    print("running self-test:", add(2, 3))

# running `python mathutils.py` prints the self-test
# `import mathutils` elsewhere does NOT run that print

What to remember

What does if __name__ == "__main__": do and why is it used?

Common footguns

  • Putting real logic (not just a demo/test) inside the __main__ guard, making it impossible for other modules to reuse that logic by importing it.

Core lesson 02

A module is a single .py file. A package is a directory of modules, historically marked by __init__.py, which runs on first import and can control what the package exposes.

Any .py file is a module — import mathutils imports mathutils.py. A package groups related modules under a directory, e.g. mypackage/geometry.py, importable as mypackage.geometry. __init__.py (even if empty) runs when the package is first imported; it's the conventional place to control what from mypackage import * exposes, or to re-export submodules for a cleaner public API.

Python example
# project structure:
# mypackage/
#   __init__.py
#   geometry.py
#   algebra.py

# mypackage/__init__.py
from .geometry import area_of_circle

# elsewhere:
from mypackage import area_of_circle   # works because __init__.py re-exported it

What to remember

What's the difference between a module and a package, and what does __init__.py do?

Common footguns

  • Circular imports between modules in the same package — module A imports from B while B imports from A, causing partial/undefined-name errors depending on import order.

Core lesson 03

Absolute imports (import mypackage.geometry) name the full path from the top-level package. Relative imports (from .geometry import area) are relative to the current module's position, using dots for how many levels up.

Absolute imports are unambiguous and generally preferred for clarity — they reference the top-level package name regardless of where the importing file lives. Relative imports (. for the current package, .. for the parent) are convenient inside a package for referring to sibling modules without hardcoding the package's name, but only work inside a package context, not in a standalone script run directly.

Python example
# inside mypackage/algebra.py
from . import geometry          # relative: sibling module
from mypackage import geometry  # absolute: same result, explicit path
from ..otherpackage import tool # relative: one level up, into a sibling package

What to remember

What's the difference between absolute and relative imports?

Common footguns

  • Using relative imports in a script meant to be run directly with python file.py — relative imports require the file to be part of a package context, raising 'attempted relative import with no known parent package'.

Python lab

Browser Python lab

Runtime · idle

Python loads on your first run. Your code stays in this browser.

Best practices

  • Prefer absolute imports for clarity, especially in larger codebases.
  • Keep __init__.py minimal — mostly re-exports, not heavy logic.
  • Avoid circular imports by moving shared code into a separate module both sides can import from.
  • Use if __name__ == "__main__": to separate reusable logic from script-only behavior.

Apply the concept in Interview practice

Design In-Memory File SystemhardLeetCode #588 · O(path length) per operation

Model directories and files as a tree of dict nodes; split paths on '/' and walk/create nodes level by level for ls/mkdir/addContentToFile/readContentFromFile.

Open problem
Design File SystemmediumLeetCode #1166 · O(path length) per operation

Store paths directly as dict keys mapping to values; createPath checks the parent path exists and the new path doesn't, mirroring how package/module paths nest.

Open problem

Concept checks

Q01

What does if __name__ == "__main__": do and why is it used?

Q02

What's the difference between a module and a package, and what does __init__.py do?

Q03

What's the difference between absolute and relative imports?