November 6, 2026•7 min read

Python Modules: the Import That Printed the Report

You pull one function out into its own file, import it, and the whole report runs again and eats your input. A module is a file, import runs it top to bottom, and one line decides which of the two jobs it is doing.

Read in Indonesian

A module is just a Python file that another file can import, so it can reuse the code inside without copying it. Any .py file you have written already counts as one. You moved the report into report.py, and a second file needed one function from it. So you wrote import report at the top and ran that second file.

The import ran the whole report. It read your input first, then printed every row and the total, all before the second file reached its next line. Nothing raised an error, so nothing told you why.

The cause is one rule about what import does, and the fix is one line plus a small rearrangement. You will see both fail and pass.

Key takeaways

  • import report runs the whole file from top to bottom at that moment, so top-level work happens again inside whatever imported it.
  • __name__ is __main__ when you run the file yourself and the module name when something imports it; the guard compares those two strings.
  • Work belongs inside functions, because a def only binds a name, and the call under if __name__ == "__main__": is what starts the work.
  • A value computed at module level is finished once: the starter's total was the number 9, so calling it raised TypeError: 'int' object is not callable.
  • The guard protects only the lines indented under it, so a stray top-level print still fires on every import.

Two files, one command

The contract is the same as in the earlier articles, such as Python Dictionaries: the Key That Isn't There. You write the program, the article hands you a checker, and you run the checker until it goes from red to green. You need two files and python3. There is no repository, no Docker and no account.

Save this as report.py. It is your script as it stands, with the report at the top level:

PYTHON
# report.py
import sys

rows = []
for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    name, qty = line.split(",")
    rows.append((name, int(qty)))

total = sum(qty for _, qty in rows)
print("item,qty")
for name, qty in rows:
    print(f"{name},{qty}")
print(f"total,{total}")

What import does to a file

The file's name is the name you import: report.py is imported as report, with the .py left off.

When Python meets import report, it finds the file and runs it from top to bottom, right then. It does this once per process. Every top-level line executes: the loop over sys.stdin, the sum, each print. Python has no idea which lines you meant as the reusable part.

Where does it look for the file? The folder of the script you ran goes first on the import path. I checked this with a one-line probe in a folder named probe. Running python3 path_probe.py there, sys.path[0] had the basename probe. That is why two files in one folder can import each other with no setup.

Now the starter, imported. It prints the full report: item,qty, kopi,3, teh,2, kopi,4, total,9. It has a second problem too. total is a finished number, not a function, so calling it raises TypeError: 'int' object is not callable.

Save the checker next to report.py. It feeds the program kopi,3, teh,2 and kopi,4, and it expects a total of 9. It runs your file once as a script and twice as an import.

PYTHON
# check_report.py
import subprocess
import sys

DATA = """kopi,3
teh,2
kopi,4
"""

EXPECTED = """item,qty
kopi,3
teh,2
kopi,4
total,9"""


def run(args, stdin=""):
    return subprocess.run(
        [sys.executable] + args,
        input=stdin,
        text=True,
        capture_output=True,
    )


def report(results):
    for label, passed, detail in results:
        mark = "PASS" if passed else "FAIL"
        note = f"  <- {detail}" if detail and not passed else ""
        print(f"{mark}  {label}{note}")
    passed = [ok for _, ok, _ in results].count(True)
    print(f"\n{passed} of {len(results)} checks passed")


def main():
    as_script = run(["report.py"], DATA)
    as_import = run(["-c", "import report"], DATA)
    as_call = run(["-c", "import report; print(report.total([('a', 1), ('b', 2)]))"])

    stderr_script = as_script.stderr.strip().splitlines()
    results = [
        ("the script runs without crashing", as_script.returncode == 0,
         stderr_script[-1] if stderr_script else ""),
        ("the script prints the report, total last", as_script.stdout.strip() == EXPECTED,
         repr(as_script.stdout)),
        ("importing it does not crash", as_import.returncode == 0, as_import.stderr.strip()),
        ("importing it prints nothing", as_import.stdout == "", repr(as_import.stdout)),
        ("total() is a function you can call", as_call.returncode == 0 and as_call.stdout.strip() == "3",
         as_call.stderr.strip().splitlines()[-1] if as_call.stderr.strip() else repr(as_call.stdout)),
    ]

    report(results)
    return 0 if all(ok for _, ok, _ in results) else 1


if __name__ == "__main__":
    sys.exit(main())

Run it:

TEXT
python3 check_report.py
TEXT
PASS  the script runs without crashing
PASS  the script prints the report, total last
PASS  importing it does not crash
FAIL  importing it prints nothing  <- 'item,qty\nkopi,3\nteh,2\nkopi,4\ntotal,9\n'
FAIL  total() is a function you can call  <- TypeError: 'int' object is not callable

3 of 5 checks passed

The exit code is 1. Run as a script, the file is fine. Both failures come from the import, and the first one shows you the whole report that leaked out of it.

One line decides which job the file is doing

Python gives every file a name while it runs. That name lives in __name__. It is __main__ when you run the file yourself, and it is the module name when something imports it.

Put a single line, print(__name__), in a file called name_probe.py. Run python3 name_probe.py and it prints __main__. Import it from another file and the same line prints name_probe.

The code is identical and the name is different. The line if __name__ == "__main__": compares those two strings. When they match, the indented block runs. When another file imports yours, they do not match and the block is skipped.

So the plan has two parts. Put the work inside functions, because a def only binds a name when the file is imported. Then put the one call that starts the work under the guard.

The fix

I split the starter into three functions. parse reads the rows, total adds them up, and main prints the report. Now total is a function, which clears the TypeError. The last two lines hold the guard.

PYTHON
# report.py
import sys


def parse(lines):
    rows = []
    for line in lines:
        line = line.strip()
        if not line:
            continue
        name, qty = line.split(",")
        rows.append((name, int(qty)))
    return rows


def total(rows):
    return sum(qty for _, qty in rows)


def main():
    rows = parse(sys.stdin)
    print("item,qty")
    for name, qty in rows:
        print(f"{name},{qty}")
    print(f"total,{total(rows)}")


if __name__ == "__main__":
    main()

Run the checker again:

TEXT
python3 check_report.py
TEXT
PASS  the script runs without crashing
PASS  the script prints the report, total last
PASS  importing it does not crash
PASS  importing it prints nothing
PASS  total() is a function you can call

5 of 5 checks passed

The exit code is 0.

What this does not do

A package is a folder with an __init__.py file, and relative imports are the from . import form used inside one. Both are for later. Two files in one folder need neither.

The guard also does not rescue a print you left at module level. If a stray print("debug") sits outside every function and outside the guard, each process that imports the file prints it once. The guard protects only the lines indented under it.

Before the next article

Delete the last two lines of the fixed file: the if __name__ == "__main__": line and the main() call under it. Run the checker again. I measured 4 of 5 and exit code 1, and the only failure is this one:

TEXT
FAIL  the script prints the report, total last  <- ''

The script runs and prints nothing. Now put the two lines back and run the checker until it says 5 of 5 again.

That proves the checker can tell whether the file runs or only imports. It cannot tell whether the code inside main() is correct.

If import runs a whole file, what else in your old scripts has been running at the top level all along?

Get better at shipping code.

Join the other developers getting tech updates, tips, and relaxed tutorials in their inbox every week.

Stay in the loop

Tech updates, coding tips, and relaxed insights straight to your inbox every week.

No spam. Unsubscribe anytime.

Read next

See all

For developers who learn by struggling

© 2026 Inva.dev.