Skip to main content

Writing your own module and package

Move mean and predict into a file of your own and import it from a script beside it. Follow Python’s search through the folders of sys.path, write a file that is both a script and a module, make a package, and avoid the trap of a file called random.py.
P01.5.2L0 Foundation25 minIn review

Before this lesson

P01.5.1 Modules and import

After this lesson you can

  • Move functions into a module file of your own and import it from a script in the same folder.
  • Walk the folders of sys.path by hand to predict where an import is found, or why it ends in ModuleNotFoundError.
  • Write a file that works both as a script and as a module, using if name == ‘main’.
  • Make a package from a folder and an empty init.py, and import a module from it with a dotted name.
  • Explain why a file of your own named random.py breaks import random, and fix it.

In review. The maths and code on this page are checked by automated tests on every change, but an independent reader has not signed it off yet. Spot a mistake or a confusing step? Tell us.

The question

Your mean and predict of P01.4 live inside one program. A second program needs them too, and copying them means two copies to keep in step: fix a mistake in one, and the other still has it. statistics has no such problem, because any program can import it. How do you make a file of your own importable in the same way, and how does Python find it?

From the last lesson

1. What three things does import statistics do?

Show answer

It finds the module, runs its file once from top to bottom, and binds the name statistics to the module object.

2. After from statistics import mean, what has happened to a mean you defined earlier?

Show answer

It has lost the name: mean now refers to the function inside statistics.

Intuition

Any file of Python code is a module. Save mean and predict in a file called stats_tools.py, put a script called report.py in the same folder, and the line import stats_tools in the script works exactly like import statistics: find, run once, bind. The module’s name is the file name without .py.

Top: a folder /home/ama/project holding two files. report.py has the lines import stats_tools and print(stats_tools.predict(2)); a green arrow labelled finds runs from it to stats_tools.py, which has the lines def mean(values) and def predict(hours). Bottom: a memory diagram for report.py. A frame of global names holds the orange name stats_tools, with an arrow to a blue module box named stats_tools that lists mean and predict, each with an arrow to a function box, mean(values) and predict(hours). A green arrow from stats_tools.py down to the module box is labelled running the file once defines mean and predict.
P01.5.2-F1

A file of your own is a module. report.py imports stats_tools.py from its own folder, and the name stats_tools then refers to a module that holds mean and predict.

Python found the file because the first place it looks is the folder of the script you ran. This lesson follows that search, one folder at a time, and ends with a trap that catches almost everyone once.

Build it up

Step 1. The module and the script. Here are the two files, then the command that runs the script, and what it printed. They are shown rather than run on this page; a hidden check writes the same files to a temporary folder and runs the same command.

stats_tools.py
print('loading stats_tools')


def mean(values):
    """Return the mean of a list of numbers."""
    return sum(values) / len(values)


def predict(hours):
    """Return the predicted quiz score for the given hours of study."""
    return 9 * hours + 33
report.py
import stats_tools
import stats_tools

scores = [55, 65, 80, 40, 60]
print('mean score:', stats_tools.mean(scores))
print('predicted for 2 hours:', stats_tools.predict(2))
$ python report.py
loading stats_tools
mean score: 60.0
predicted for 2 hours: 51

loading stats_tools appears once, although report.py imports the module twice. The second import found the module already made, and only bound the name again.

Step 2. Where Python looks. The module sys holds sys.path, a list of folders. Import tries them in order and stops at the first one that holds the module. A for loop prints them. The output below is from an imaginary Linux machine with Python 3.14 installed in /usr/local; yours will show other folders.

import sys

for folder in sys.path:
    print(folder)
/home/ama/project
/usr/local/lib/python314.zip
/usr/local/lib/python3.14
/usr/local/lib/python3.14/lib-dynload
/usr/local/lib/python3.14/site-packages

From the top: the folder of the script that was run (at the interactive prompt, the current folder, written as an empty string); a zip file of the standard library, listed even when it does not exist; the standard library itself, where statistics.py and random.py live; its compiled modules; and site-packages, where installed packages go (P01.5.3). Before any folder, Python checks the few modules that are built into the interpreter itself, sys among them.

The statement import statistics above a column of six boxes tried from the top, with a green arrow down the left side. First, built into the interpreter: sys, builtins and a few more, marked with a cross. Then folders 1 to 5 of sys.path: 1 /home/ama/project with report.py and stats_tools.py, cross; 2 /usr/local/lib/python314.zip, dashed because it may not exist, cross; 3 /usr/local/lib/python3.14 with os.py, random.py and statistics.py, where statistics.py is on a yellow band and a tick marks the folder; 4 lib-dynload and 5 site-packages are grey. Notes on the right: built-in modules come first; then the folders of sys.path, from the top; found, Python stops here; never looked at.
P01.5.2-F2

The search for statistics. It passes the built-in modules and two folders, stops at the third, and never looks at the last two.

Step 3. A file that is both a script and a module. Every module has a global name __name__, with two underscores on each side. Python sets it to '__main__' in the file it was asked to run, and to the module’s own name in a file that is imported. An if at the end of stats_tools.py uses that to run a short demo only when the file itself is run:

end of stats_tools.py
if __name__ == '__main__':
    print('demo: the mean of the scores is', mean([55, 65, 80, 40, 60]))
$ python stats_tools.py
loading stats_tools
demo: the mean of the scores is 60.0
$ python report.py
loading stats_tools
mean score: 60.0
predicted for 2 hours: 51
Two flow charts of stats_tools.py side by side. Left, headed python stats_tools.py and __name__ is '__main__': start of stats_tools.py, then define mean, predict, then a diamond asking __name__ == '__main__'. The True exit, drawn thick and green, runs down to print the demo and then to end; the False exit is grey and dashed. Right, headed python report.py, it imports stats_tools, and __name__ is 'stats_tools': the same chart, but the thick green path leaves the diamond by the False exit straight to end, and print the demo is grey.
P01.5.2-F5

One file, two ways to run it. Run as a script, the demo runs; imported, it is skipped.

Step 4. A package: a folder of modules. A package is a folder of modules with a file called __init__.py, which may be empty. Here the same stats_tools.py moves into a folder study:

project/
    report.py
    study/
        __init__.py
        stats_tools.py
report.py, using the package
import study.stats_tools

scores = [55, 65, 80, 40, 60]
print('mean score:', study.stats_tools.mean(scores))
$ python report.py
loading stats_tools
mean score: 60.0

import study.stats_tools binds one name, study, to the package. The module is reached through it with a second dot. Large libraries such as NumPy and scikit-learn are packages of this kind, with packages inside packages.

The formal statement

NoteDefinition: the module search, __name__, package

When import name runs for a module not yet imported in this run, Python checks the modules built into the interpreter, then tries the folders of sys.path in order, looking for name.py, a package folder name or a compiled module. It takes the first match, runs it and binds it as in P01.5.1. If nothing matches, it raises ModuleNotFoundError.

A module’s __name__ is '__main__' when its file is the program that was run, and the module’s own name when it is imported.

A package is a module that holds modules: a folder with a file __init__.py, which runs when the package is imported. A module inside it has a dotted name, such as study.stats_tools.

Two consequences follow.

  • The first match wins. A folder near the top of sys.path beats every folder below it, even one that holds a better match.
  • Your folder comes before the standard library. The Python Tutorial (section 6.1.2) warns that a file in the script’s folder “will be loaded instead of” a library module of the same name, and calls this “an error unless the replacement is intended”. The pitfalls below show the error.
Go deeper: what happens to a folder without __init__.py

Leave out __init__.py and import study.stats_tools still works: a plain folder can be imported as a namespace package, which the Tutorial calls a relatively advanced feature. Write __init__.py anyway. It says that the folder is a package on purpose, and the Tutorial (section 6.4) gives the reason the rule exists: it stops a folder with a common name, such as string, from hiding a real module further down the search path.

Worked example

Walk the search by hand for three imports, using the folders of Step 2 and the files in them: import stats_tools, import statistics, and import stats_tool, with a letter missing.

  • stats_tools: not built in. Folder 1 holds stats_tools.py, so the search stops there.
  • statistics: not built in, not in folder 1, and folder 2 does not exist. Folder 3 holds statistics.py, so the search stops there; folders 4 and 5 are never tried.
  • stats_tool: not built in, and no folder holds stats_tool.py. The search runs out of folders.
A table. The rows are the places tried, in order: built in, 1 project folder, 2 python314.zip, 3 standard library, 4 lib-dynload, 5 site-packages, and a last row, result. The columns are three imports: stats_tools, statistics and stats_tool. For stats_tools: built in, not here; folder 1, a tick and found; every later row, not tried; result, from folder 1. For statistics: crosses for built in and folders 1 and 2, a tick and found at folder 3, not tried below; result, from folder 3. For stats_tool: a cross in every row; result, ModuleNotFoundError in reddish purple.
P01.5.2-F3

The search walked by hand. A search stops at its first match; the misspelt name is tried everywhere and found nowhere.

The third import, run as line 1 of report.py, ends like this (Python 3.15.2, which also suggests the right name; Python 3.12 prints the same message without the suggestion):

Traceback (most recent call last):
  File "report.py", line 1, in <module>
    import stats_tool
ModuleNotFoundError: No module named 'stats_tool'. Did you mean: 'stats_tools'?

Python prints the full path of each file; in this module it is cut down to the file name.

Code

This page cannot run your files as scripts, but it can import stats_tools: a hidden cell has written the file into a temporary folder and put that folder at the top of sys.path.

import stats_tools
import stats_tools

print(stats_tools.mean([55, 65, 80, 40, 60]))
print(stats_tools.predict(2))
print(stats_tools.__name__)
loading stats_tools
60.0
51
stats_tools

Now the search itself, from scratch. os.path.join puts a folder and a file name together with the right separator, and os.path.exists says whether a file is there. Every module that came from a file keeps that file’s path as its __file__, so the result can be checked against what import found.

import os
import statistics
import sys


def find_file(name):
    """Walk sys.path as import does; return the first name.py found, or None."""
    for folder in sys.path:
        path = os.path.join(folder, name + '.py')
        if os.path.exists(path):
            return path
    return None


print('statistics, same file as import: ', find_file('statistics') == statistics.__file__)
print('stats_tools, same file as import:', find_file('stats_tools') == stats_tools.__file__)
print('stats_tool:', find_file('stats_tool'))
statistics, same file as import:  True
stats_tools, same file as import: True
stats_tool: None

The real search does more: it finds packages and compiled modules too, and it checks the built-in modules first. sys.builtin_module_names lists those, and a loop can look for one name in it.

def is_built_in(module_name):
    """True when the module is compiled into the interpreter itself."""
    for name in sys.builtin_module_names:
        if name == module_name:
            return True
    return False


print('sys:       ', is_built_in('sys'))
print('statistics:', is_built_in('statistics'))
sys:        True
statistics: False

Explore

This module’s interactive, the import resolver, lives in lesson P01.5.4. Try it now: choose stats_tools, then statistics, and step the search; then switch on the file random.py and import random.

Pitfalls

Top: dice.py, in the project folder, with the lines import random, random.seed(1), print(random.randint(1, 6)). Two panels below. Left, marked with a cross and titled your random.py: a search column in which folder 1, the project folder, holds dice.py and random.py, with random.py on a reddish purple band and a cross; the green search arrow stops there, and folder 3, the standard library with the real random.py, is grey. Below it in reddish purple: AttributeError: module 'random' has no attribute 'seed'. Right, marked with a tick and titled renamed try_random.py: folder 1 holds dice.py and try_random.py and is crossed; the arrow goes on to folder 3, where random.py is on a yellow band with a tick. Below: the real random is found, and dice.py prints a number.
P01.5.2-F4

Wrong beside right. Your own random.py, in the folder searched first, stands in front of the real one; renamed, it is passed by.

“A file of mine called random.py is harmless.”

Say you tried out random in a file named random.py, which prints a line when it runs, and then wrote dice.py beside it, the program at the top of the figure. Running dice.py imports your file, because its folder comes first:

$ python dice.py
my own random.py is running
Traceback (most recent call last):
  File "dice.py", line 3, in <module>
    random.seed(1)
    ^^^^^^^^^^^
AttributeError: module 'random' has no attribute 'seed' (consider renaming 'random.py' since it has the same name as the standard library module named 'random' and prevents importing that standard library module)

Python 3.13 added the advice in brackets; Python 3.12 stops after 'seed'. It is the same mistake as naming a function of your own sum, which hides the built-in (P01.4.4). Rename the file, for example to try_random.py, and keep the names of standard modules, such as random, statistics, math and string, for the real ones.

“To import stats_tools.py, write its file name.”

import stats_tools.py asks for a module named py inside a package stats_tools. Python imports stats_tools first, which is why loading stats_tools appears, and then gives up:

loading stats_tools
Traceback (most recent call last):
  File "report.py", line 1, in <module>
    import stats_tools.py
ModuleNotFoundError: No module named 'stats_tools.py'; 'stats_tools' is not a package

Import a module by its name, without .py.

“I edited stats_tools.py, so the next import uses the new code.”

A module runs once per run of Python. A script starts a new run each time, so it sees your edit; a notebook or an open interactive prompt keeps the module it loaded first. Restart it after changing your module. Hidden old state of this kind is the classic notebook bug of P02.9.

Check yourself

Recall

1. Where does Python look first for a module that is not built into the interpreter?

Show answer

In the folder of the script that was run, the first entry of sys.path. At the interactive prompt, it is the current folder.

2. What does a second import stats_tools in the same run do?

Show answer

It finds the module already made and binds the name again. The file does not run a second time.

3. What is __name__ inside stats_tools.py when you run python stats_tools.py, and when report.py imports it?

Show answer

'__main__' when it is run as the program; 'stats_tools' when it is imported.

4. What makes the folder study a package, and how do you import stats_tools from it?

Show answer

The file __init__.py inside it, which may be empty. Write import study.stats_tools.

5. Why does a file named random.py in your project break import random?

Show answer

The script’s folder comes first in sys.path, so the search finds your file and stops there. The real random in the standard library is never reached.

Exercises

1. ★ With the five folders of Step 2, where does each import end? import report, import random, import numpy (on a machine where NumPy has been installed, P01.5.3), and import ststistics.

Show solution

report is found in folder 1, the project folder, as report.py; importing it would run the script. random is found in folder 3, the standard library. numpy is found in folder 5, site-packages, where installed packages go. ststistics is in no folder, so the search ends in ModuleNotFoundError.

2. ★★ Write down everything that python greet.py prints, and then everything that python main.py prints.

greet.py
print('A')


def hello():
    print('B')


if __name__ == '__main__':
    hello()
    print('C')
main.py
import greet
greet.hello()
import greet
Show solution

python greet.py prints A, B, C. The file is the program, so __name__ is '__main__' and the last two lines run after the top-level print.

python main.py prints A, then B. The first import runs greet.py as a module: it prints A, defines hello, and skips the guarded lines, because there __name__ is 'greet'. Then greet.hello() prints B. The second import does nothing, because the module is already loaded.

3. ★★★ An old copy of stats_tools.py, in which predict returns 10 * hours + 30, sits in a folder that comes later in sys.path than the project folder. Which value does report.py print for stats_tools.predict(2), 51 or 50? How could you check which file was loaded? And does the answer change if you run the script from another folder, as python project/report.py?

Show solution

It prints 51. The project folder comes first, and the first match wins, so the old copy is never reached. To check, print stats_tools.__file__, the path of the file the module came from.

Running python project/report.py from the folder above changes nothing. For a script, the first entry of sys.path is the folder that holds the script, not the folder you typed the command in, so the project folder still comes first.

Summary card

Your file is a module; Python finds it by trying the folders of sys.path from the top.

The anchor figure again: a project folder holding report.py and stats_tools.py, an arrow from one to the other, and the memory diagram in which the name stats_tools refers to a module that holds mean and predict.
P01.5.2-F1

Your own file, imported by name.

  • Module name = file name without .py; a script imports a module from its own folder.
  • Search order: built-in modules, then the folders of sys.path, first match wins; no match means ModuleNotFoundError.
  • if __name__ == '__main__': runs lines only when the file is the program that was run.
  • Package: a folder with __init__.py; import study.stats_tools.
  • Never name a file of your own after a standard module, such as random.py.

Sources and next steps

Sources. Each was opened and checked against this lesson on 12 November 2026.

  • Python Software Foundation, The Python Tutorial, chapter 6, Modules. Section 6.1.1: run as python fibo.py, a module’s code runs with __name__ set to "__main__", and a guarded block does not run when the module is imported. Section 6.1.2: Python first looks for a built-in module, then for a file in the folders of sys.path, which starts with the script’s folder; scripts there are loaded instead of library modules of the same name, “an error unless the replacement is intended”. Section 6.4: __init__.py makes a folder a package, may be empty, and keeps folders with common names from hiding real modules.
  • Python Software Foundation, sys.path: the script’s folder is put first, or an empty string meaning the current folder at the prompt; and sys.builtin_module_names, the modules compiled into the interpreter.
  • Python Software Foundation, The initialization of the sys.path module search path: the zip file of the standard library is listed even if it does not exist, and site-packages is added last.
  • Python Software Foundation, The Python Language Reference, chapter 5, The import system: section 5.2.1, a regular package is a folder with an __init__.py, which runs when the package is imported; section 5.3, a module found nowhere raises ModuleNotFoundError.
  • Python Software Foundation, What’s New in Python 3.13, Improved error messages: the advice to rename a script that has the name of a standard-library module.

Next. P01.5.3 Installing packages with pip fills the last folder, site-packages, with code written by others.

This lesson unlocks

P01.5.3 Installing packages with pip

Your progress is saved in this browser only. No account, no tracking.
Back to top