Modules, Packages, and Environments
Import code across files correctly, understand what the if __name__ == "__main__" guard actually does, and keep dependencies isolated with a virtual environment.
By the end of this lesson, you can
- Explain what __name__ is set to when a file runs directly versus when it's imported
- Write the if __name__ == "__main__" guard correctly, and explain what it protects against
- Distinguish a virtual environment's isolated packages from a system-wide install
- Evaluate a third-party package responsibly before adding it as a dependency
Why it matters
Every example so far has ended with the same two lines — if __name__ == "__main__": main() —
without a full explanation of what they do or why they matter. Once a program grows past one file,
understanding that pattern (and where packages actually get installed) is what separates code that
works only as a standalone script from code that’s genuinely reusable.
Mental model
Every module — every .py file — is a namespace: a mapping from names to the objects defined
in it. import greeting_helpers doesn’t dump greeting_helpers’s names into the importing file’s
own namespace; it makes the whole module available as one name, and greeting_helpers.greet(...)
reaches inside it explicitly. This is exactly why a name like pi can exist inside math without
ever colliding with an unrelated local variable also called pi.
Every module also has a __name__ variable, set automatically depending on how the file started
running: "__main__" if it was executed directly, or the module’s own name if another file
imported it instead. That’s the entire mechanism behind the guard every example on this site ends
with.
Worked example: one file imports another
"""A tiny helper module — meant to be imported, not run directly."""
def greet(name: str) -> str:
return f"Hello, {name}!"
if __name__ == "__main__":
print("This module is meant to be imported, not run directly.")import math
import greeting_helpers
def main() -> None:
print(greeting_helpers.greet("Ada"))
print(f"pi is approximately {math.pi:.4f}")
print(f"__name__ here is: {__name__!r}")
if __name__ == "__main__":
main()Hello, Ada!
pi is approximately 3.1416
__name__ here is: '__main__'Running modules_and_environments.py directly sets its own __name__ to "__main__", so its
guard’s main() call runs. greeting_helpers is imported, not run directly — its own guard
never fires, which is exactly why greeting_helpers.py’s “this module is meant to be imported”
message never appears in the output above, even though that file’s code did execute (its def greet(...): ran, defining the function modules_and_environments.py then calls).
Common mistakes
- Writing side-effecting code (a
print, a file write, a network call) at module level, outside any function and outside the guard — it runs every time the file is imported, not just when run directly, which is rarely what’s intended. - Defining functions or classes inside the guard — this makes them unavailable to anything that imports the file, since the guard’s body never runs on import. Definitions go outside the guard; only the “do something now” call belongs inside it.
- Installing packages globally (no active virtual environment) and being surprised later when two projects need incompatible versions of the same dependency.
Check your understanding
Or reveal the answer without checking
Answer:"tools.py is loading"
The print("tools.py is loading") line is outside the guard, so it runs whenever the file is loaded at all — including by an import. The print(helper()) call is inside the guard, so it only runs when tools.py itself is executed directly, not when it's imported.
Or reveal the answer without checking
Answer:Give each project its own virtual environment, so each can have the version it needs
A virtual environment isolates each project's installed packages from every other project's — this exact version-conflict scenario is the main reason virtual environments exist rather than installing everything system-wide.
Practice: warm-up
Predict, then check: if greeting_helpers.py (from the worked example) is imported twice from
different files in the same program run, does its module-level code — including any print
statements outside the guard — run twice?
Stuck? Reveal one hint at a time.
Hint 1
Python caches every module it imports, in a structure it consults before running a module's code again.
Hint 2
The second import (from anywhere in the same running program) finds the already-loaded module in that cache and reuses it directly.
Hint 3
No — a module only actually runs once per program execution, no matter how many different files import it. Every import after the first just retrieves the same, already-created module object.
Reveal the answer
Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.
File A: import greeting_helpers # greeting_helpers.py's code runs, once
File B: import greeting_helpers # reuses the already-loaded module — no re-runPython caches every module by name the first time it’s imported (sys.modules), so a second
import of the same module anywhere else in the same running program is a cache lookup, not a
re-execution.
Practice: apply it
Turn a small script into two files: a stats.py module with a reusable mean(numbers: list[float]) -> float function, and a separate script that imports and calls it. Predict what happens if you
accidentally define mean’s logic inside the if __name__ == "__main__": guard instead of at
module level.
Or reveal the answer without checking
Answer:An AttributeError, because mean was never defined at module level — only inside the guard, which never ran on import
A def inside the guard only executes (and so only creates the function) when the file runs as __main__. On import, the guard's body never runs, so stats.mean simply doesn't exist yet — accessing it raises AttributeError: module 'stats' has no attribute 'mean'.
Summary
- Every module is its own namespace;
import module_namemakes that whole namespace available as one name rather than merging its contents into the importer’s own namespace. __name__is"__main__"when a file runs directly, and the module’s own name when another file imports it — the entire mechanism behindif __name__ == "__main__":. Definitions go outside the guard; only “run this now” code belongs inside it.- A module only actually executes once per program run, no matter how many files import it — Python caches already-imported modules.
- A virtual environment isolates a project’s installed packages from the system Python and from other projects; evaluate a third-party package’s maintenance, license, and trustworthiness before adding it as a dependency.