What you'll learn
- Split a program across files and import from one to another
- Understand the main guard,
if __name__ == "__main__":, and why every script has one - Organize modules into a package with a folder and
__init__.py, and use relative imports inside it - Control what a module exports with
__all__, and recognize a circular import when it bites
Concept
Every .py file is a module. When you import helpers, Python finds helpers.py, runs it top to bottom once, and gives you its names under helpers., so helpers.clean(text) calls the function defined there. from helpers import clean brings the one name in directly. Running a file once and caching the result is why imports are cheap to repeat and why code at the top level of a module runs at import time, whether you wanted it to or not.

Where does Python look? The folder of the script you ran comes first, then the standard library, then installed packages. That first entry is why import helpers works when helpers.py sits next to your script and fails when it does not. The full list is sys.path; you rarely change it, but knowing it exists explains most "module not found" errors.
Every module has a __name__. When a file is run directly, its __name__ is "__main__"; when it is imported, its __name__ is its module name. The main guard, if __name__ == "__main__":, therefore means "only when this file is the program, not when it is imported". Put the code that starts your program under it, and the same file becomes both a runnable script and an importable library of functions.
| Situation | __name__ in tools.py |
Code under the main guard |
|---|---|---|
python tools.py |
"__main__" |
runs |
import tools from elsewhere |
"tools" |
does not run |
from shop.tools import x |
"shop.tools" |
does not run |
A package is a folder of modules with an __init__.py file, which can be empty. It gives related modules one namespace: from shop.pricing import tax. Inside a package, modules import their siblings with relative imports, from .pricing import tax, so the package can be renamed or moved without editing every file. __all__, a list of names in a module, declares its public API: from module import * imports only those, and readers know what is meant for outside use.

A circular import happens when a imports b and b imports a. Python starts running a, hits the import of b, runs b, which imports the half-finished a and fails to find names that have not been defined yet. The fix is structural: move the shared code into a third module both can import, or import inside the function that needs it. The traceback is the clue: it shows circular_a importing circular_b, which imports circular_a again, and then a name that does not exist yet. Depending on the situation Python's last line says "partially initialized module" or suggests renaming the file; either way, the loop in the traceback is the real cause.
Code
Two files: a module of functions and a script that imports them. The module's own test code is under a main guard, so importing it prints nothing.
# textkit.py
"""Small text helpers."""
__all__ = ["clean", "initials"]
def clean(text):
return " ".join(text.split()).strip().lower()
def initials(name):
return "".join(part[0] for part in clean(name).split()).upper()
def _debug(text):
return repr(text)
if __name__ == "__main__":
print("running textkit directly:", clean(" Hello World "))
running textkit directly: hello world# main.py
import textkit
from textkit import initials
print(textkit.__name__, "|", __name__)
print(textkit.clean(" Ada Lovelace "))
print(initials("grace brewster hopper"))
print(textkit.__all__)
textkit | __main__
ada lovelace
GBH
['clean', 'initials']A package with a relative import inside it. cart.py reaches its sibling with a leading dot, and main imports through the package name.
# shop/__init__.py
"""The shop package."""
# shop/pricing.py
TAX_RATE = 0.2
def tax(amount):
return round(amount * TAX_RATE, 2)
# shop/cart.py
from .pricing import tax
def total(prices):
subtotal = sum(prices)
return subtotal + tax(subtotal)
# use_shop.py
from shop.cart import total
from shop import pricing
print(pricing.__name__)
print(total([10.0, 25.0]))
shop.pricing
42.0A circular import caught in the act. Read the traceback top to bottom: run_circular imports circular_a, which imports circular_b, which imports circular_a and asks for greet before that line has run. The last block shows that the first entry on the search path is the script's own folder.
# circular_a.py
import circular_b
def greet():
return "a greets " + circular_b.name()
# circular_b.py
import circular_a
def name():
return "b"
print(circular_a.greet())
# run_circular.py
import circular_a
Traceback (most recent call last):
File "run_circular.py", line 1, in <module>
import circular_a
File "circular_a.py", line 1, in <module>
import circular_b
File "circular_b.py", line 6, in <module>
print(circular_a.greet())
^^^^^^^^^^^^^^^^
AttributeError: module 'circular_a' has no attribute 'greet' (consider renaming 'circular_a.py' if it has the same name as a library you intended to import)# show_path.py
import sys
from pathlib import Path
print(Path(sys.path[0]).resolve() == Path(__file__).resolve().parent)
print(len(sys.path) > 2)
print(any("lib" in p for p in sys.path))
True
True
TrueTry it yourself
Create a package geometry/ with an empty __init__.py, a shapes.py defining area_circle and area_square, and a report.py that imports them with a relative import and defines describe(shape, size). Write main.py outside the package that imports describe and prints two descriptions. Give shapes.py a main guard that prints a quick self-test, and confirm it only prints when run directly.
Common mistakes
- Running a module inside a package directly.
python shop/cart.pybreaks the relative import; run from outside withpython -m shop.cartor import it from a script. - Top-level code that does work. Anything not under the main guard runs on import; a module that prints or opens files at import time surprises every importer.
- Naming your file after a standard module. A file called
random.pyorjson.pynext to your script shadows the real one, and the error will point somewhere confusing.
What's next
- Every
.pyfile is a module;importruns it once and caches it, and the search path starts with your script's folder. if __name__ == "__main__":separates the script part from the importable part.- A folder with
__init__.pyis a package; use relative imports inside it,__all__to declare the public names, and restructure when imports go circular.
Your code can span many files. Next you will isolate each project's dependencies with virtual environments, pip, and uv.