Skip to content
VLSI Mentor

Python Part I · Module 13 · Modules, Packages and Imports

Importing Your Own File

A script that works grows. The checker that was forty lines becomes four hundred, half of it log parsing and half of it reporting, and every change to one half means scrolling past the other. Chapter 11.15 drew the line that says where to cut; this chapter is the mechanism that acts on it. The encouraging part is that there is almost nothing to learn — your file and the math module are the same kind of thing to Python, and the statement that reaches one reaches the other. The part worth care is that importing a file runs it, and that where you launch Python from decides what it can find.

Foundation16 min readPythonPart IFundamentalsModulesProject Structure

Part I · Module 13 · Chapter 13.5 · Importing Your Own File

1. The File That Grew

A log checker that does two separable things:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(lines):
    """Return the number of UVM_ERROR lines."""
    n = 0
    for line in lines:
        if "UVM_ERROR" in line:
            n += 1
    return n


def summarise(count):
    """Return a one-line verdict for an error count."""
    if count == 0:
        return "PASS"
    return "FAIL (" + str(count) + " errors)"


log = ["UVM_ERROR a", "INFO b", "UVM_ERROR c"]

print(summarise(count_errors(log)))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
FAIL (2 errors)

At this size there is no problem. At four hundred lines there are two: finding anything, and the fact that a change to reporting sits in the same file as parsing, so both are touched by every commit.

2. Two Files

Azvya Education Pvt. Ltd.VLSI Mentor
parse.py
def count_errors(lines):
    """Return the number of UVM_ERROR lines."""
    n = 0
    for line in lines:
        if "UVM_ERROR" in line:
            n += 1
    return n
Azvya Education Pvt. Ltd.VLSI Mentor
run.py
from parse import count_errors

log = ["UVM_ERROR a", "INFO b", "UVM_ERROR c"]

print(count_errors(log))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2

That is the whole mechanism. parse.py sits beside run.py, and from parse import count_errors reaches into it exactly as from math import sqrt reaches into the math module.

The module name is the filename without .py. A file called parse.py is imported as parse. There is no registration step and no declaration — the file's existence beside the one importing it is the entire arrangement.

Both import forms work, and Chapter 13.2's rule for choosing still applies:

Azvya Education Pvt. Ltd.VLSI Mentor
parse.py
def count_errors(lines):
    """Return the number of UVM_ERROR lines."""
    n = 0
    for line in lines:
        if "UVM_ERROR" in line:
            n += 1
    return n
Azvya Education Pvt. Ltd.VLSI Mentor
run.py
import parse

log = ["UVM_ERROR a", "UVM_ERROR b"]

print(parse.count_errors(log))
print(parse.__name__)
print("count_errors" in dir(parse))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2
parse
True

3. Where to Cut

Chapter 11.15 answered this already. The boundary that mattered inside one file is the boundary that matters between files: the part that only transforms data, and the part that touches the outside world.

Azvya Education Pvt. Ltd.VLSI Mentor
parse.py
def count_errors(lines):
    """Return the number of UVM_ERROR lines."""
    n = 0
    for line in lines:
        if "UVM_ERROR" in line:
            n += 1
    return n


def summarise(count):
    """Return a one-line verdict for an error count."""
    if count == 0:
        return "PASS"
    return "FAIL (" + str(count) + " errors)"
Azvya Education Pvt. Ltd.VLSI Mentor
run.py
from parse import count_errors, summarise

logs = {
    "axi_smoke": ["INFO ok"],
    "axi_burst": ["UVM_ERROR a", "UVM_ERROR b"],
}

for name in sorted(logs):
    print(name, "->", summarise(count_errors(logs[name])))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
axi_burst -> FAIL (2 errors)
axi_smoke -> PASS

parse.py holds the pure functions — given lines, return a number; given a number, return a verdict. run.py holds the arrangement: which logs, in what order, printed where.

The payoff is the one Chapter 11.15 named. parse.py can now be imported by a different script — a nightly summary, a one-off investigation, a check — without any of run.py coming with it.

4. The Import Lists What You Depend On

Chapter 13.2 Section 7 said the import block is a dependency summary. That becomes far more useful once your own files are in it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
from parse import count_errors, summarise
import json

A reader now knows this script uses two of your functions and reads or writes JSON, before reading a line of logic. And parse appearing in several scripts is a direct signal that it is shared infrastructure rather than one file's private helper.

5. Importing a File Runs It

Chapter 13.1 Section 6 established that a module's top-level code runs on first import. With your own files that stops being a curiosity:

Azvya Education Pvt. Ltd.VLSI Mentor
parse.py
def count_errors(lines):
    """Return the number of UVM_ERROR lines."""
    return len(lines)


print("parse.py: top level ran")
Azvya Education Pvt. Ltd.VLSI Mentor
run.py
from parse import count_errors

print("run.py: starting")
print(count_errors(["a", "b"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
parse.py: top level ran
run.py: starting
2

parse.py: top level ran printed first — before run.py had done anything. The import executed the whole of parse.py, top to bottom, and only then handed back the name.

With a print that is merely untidy. With real work at the top level — submitting jobs, deleting a directory, launching a simulator — importing the file does that work, and the script doing the importing never asked for it.

6. Where You Run From Decides What Is Found

This is the most common real failure, and the code is not what is wrong.

Python looks for parse in the directory it is running from. Launch run.py from beside parse.py and it is found. Launch it from somewhere else and it is not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
project/
    parse.py
    run.py

$ cd project && python3 run.py        -> works
$ cd project/.. && python3 project/run.py   -> may not

The error is the one from Chapter 13.1:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ModuleNotFoundError: No module named 'parse'

A file you can see, in a directory you are looking at, reported as missing. The usual causes are running from the wrong directory, or a project laid out so that the two files are not actually beside each other.

Chapter 13.7 covers laying out a project so this is answerable rather than guesswork. The habit worth starting now: when ModuleNotFoundError names one of your own files, check where you launched Python from before you check the code.

7. Two Files That Import Each Other

Splitting badly produces one more failure worth recognising. Here first needs something from second, and second needs something from first:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
first.py      from second import helper
second.py     from first import start
run.py        import first

Running run.py raises ImportError, and the message ends with the phrase consider renaming ... if it has the same name as a library you intended to import, naming the full path of the file on your machine. (That path is why this chapter describes the message rather than quoting it — it would be different on yours.)

The sequence is worth following. run.py imports first, which starts running and immediately imports second, which imports first — but first is still half-way through its own first import and has not defined start yet. There is nothing to hand over.

What is genuinely surprising is that the module form survives it:

Azvya Education Pvt. Ltd.VLSI Mentor
first.py
from second import helper


def start():
    """Return a string built with help from the second module."""
    return "first: " + helper()
Azvya Education Pvt. Ltd.VLSI Mentor
second.py
import first


def helper():
    """Return this module's contribution."""
    return "second"
Azvya Education Pvt. Ltd.VLSI Mentor
run.py
import first

print(first.start())
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
first: second

The only change is import first instead of from first import start in second.py, and it works. The reason is Chapter 13.2's distinction doing real work: from first import start needs the name right now, at import time, and it does not exist yet. import first only binds the module, and first.start is not looked up until helper is actually called — by which point both files have finished loading.

8. Predict, Run, Observe

Azvya Education Pvt. Ltd.VLSI Mentor
util.py
VALUE = 10


def double(n):
    """Return n doubled."""
    return n * 2


print("util: loaded")
Azvya Education Pvt. Ltd.VLSI Mentor
main.py
import util
from util import double

print("A:", double(21))
print("B:", util.VALUE)
print("C:", util.__name__)
print("D:", "double" in dir(util), "VALUE" in dir(util))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
util: loaded
A: 42
B: 10
C: util
D: True True

The first line is util: loaded, before any of A to D — the import ran the file.

A — the directly imported name works.

B — module-level names are reachable through the module, not only functions.

C — the module's name is the filename without .py.

D — dir() works on your module exactly as it does on math.

Note that util: loaded appears once, despite two import statements. Chapter 13.1 Section 6's rule, on your own file.

9. Exercises

Exercise 1 — split one. Take the Section 1 script and split it into parse.py and run.py. Confirm the output is unchanged.

Exercise 2 — both forms. Import your parse.py with import parse and with from parse import count_errors. Say which you would use and why, citing Chapter 13.2's rule.

Exercise 3 — the module name. Rename parse.py to log_tools.py and fix the importer. Say what the module name is now and what decided it.

Exercise 4 — it runs. Add a print to the top level of your imported file and run the importer. Say where the output appears in the order and what that proves.

Exercise 5 — the dangerous version. Describe, in three sentences, what would happen if an imported file launched a simulation at its top level and three different scripts imported it.

Exercise 6 — run from elsewhere. Create two files, confirm they work, then run the entry point from the parent directory. Record what happens and explain it without referring to the code.

Exercise 7 — circular. Build two files that import each other and record the exact error. Then fix it by moving the shared function into a third file.

Exercise 8 — what belongs where. For each, say whether it belongs in the imported module or the entry script: (a) a function that counts errors (b) the list of logs to check (c) a verdict formatter (d) the loop that prints the report (e) a constant naming the error marker.

10. Summary

  • A file beside yours is importable with no registration — the file's existence is the whole arrangement.
  • The module name is the filename without .py.
  • Your module and math are the same kind of thing — a module object with a __name__, inspectable with dir().
  • Both import forms work, and Chapter 13.2's rule for choosing is unchanged.
  • Cut along Chapter 11.15's boundary: pure transformation in the module, arrangement and output in the entry script.
  • The payoff is reuse — a second script can import the module without the first script coming with it.
  • Importing a file runs it, top to bottom, before handing back any name.
  • So a file meant to be imported should define at the top level and act only inside functions.
  • ModuleNotFoundError on your own file usually means you ran from the wrong directory, not that the code is wrong.
  • Two files importing each other is a circular import. The from X import Y form fails because the name does not exist yet; import X survives because the lookup is deferred to call time.
  • Surviving it is not fixing it — two files that need each other are one file, or they share a third.

The next chapter is the one line that makes a file safe to both run and import.

Where this fits

Part of the Python curriculum.