Concepts / Creating and Organizing Modules

Creating and Organizing Modules

An import statement tells Python to find a module file, execute its code, and make its contents available to your program.

  • Programming
Interactive lab

Try it: What import Really Does

What happens on import: Python checks sys.modules, searches sys.path in order (first match wins), creates the module object, runs its top level once, then binds a name — the module, one function, or an alias — in the importing namespace; plus packages, __name__ == "__main__", ModuleNotFoundError and ImportError.

How it works

  1. If the name is already in sys.modules, reuse that module object: its code does not run again.
  2. Otherwise search each sys.path entry in order (the script's folder first); the first match wins, none raises ModuleNotFoundError.
  3. Create an empty module object, store it in sys.modules, then run the file's top level once (its __name__ is the module name, so a __main__ guard is skipped).
  4. import m binds m to the module; import m as x binds x; from m import f binds only f (ImportError if m has no f).
  5. A package runs its __init__.py first; submodules are found on the package's __path__ and become attributes of the package.

Default run (19 steps): $ python main.py — sys.path starts as [/project, /venv/site-packages, /usr/lib/python3.12]: the script's folder, then site-packages, then the standard library. Only our own modules are shown in sys.modules below. … Done. Printed: helpers loaded, __name__ = helpers | Hi Ann | Hi Ann.

Simplified: A fixed virtual project (main.py, helpers.py, pkg/, a site-packages folder and a one-file stand-in for the standard library). Built-in and frozen modules, .pyc caching, relative imports and namespace packages are not modelled; only our own modules are listed in sys.modules.

Educational simulation

Loading the simulation…

The Hidden Work Behind import

An import statement is an instruction to Python to locate a module, load it, execute its code, and make the module's contents available to the importing program. It is not merely a variable declaration or a function definition. Writing an import starts a search-and-execute process that Python performs behind the scenes.

import statementSearch for modulesys.pathLoad module fileExecute module codefirst importModule contentsavailable to programrequestsfinds matchloadsmakes names available
What happens in order when Python encounters an import statement?

Following the Search Path

For a module that is not built into Python, the interpreter uses a list called sys.path. This list contains directories, and Python checks those directories in a specific order for a file matching the requested module name. The search stops as soon as Python finds the first match.

sys.pathordered directoriesFirst directorycheck for matchNext directoryif no matchMatching module filestop searchingNo matching filesearch does not find modulechecks in ordermatchno matchmatchno match
How does the ordered list in sys.path control where Python looks for a requested module?
Worked example

A Module Name Appears in Two Directories

Suppose Python is asked to import a non-built-in module named settings, and two directories in sys.path contain a matching module file.

  1. 1
    Start with sys.path

    Python begins with the first directory listed in sys.path.

  2. 2
    Check directory order

    Python checks each directory in the order provided by sys.path.

  3. 3
    Stop at the first match

    If the requested module is found in an earlier directory, Python uses that file and does not continue to a later matching file.

  4. 4
    Recognize the naming risk

    The selected file depends on the search order, so a file with the same name as another module can create a naming conflict.

Result

The first matching module file in sys.path controls which module Python loads.

First Import and Later Imports

When Python finds a module, it executes the code in the module's body during initialization. That initialization happens only once, the first time the module is imported. Python then caches the loaded module in memory. If the same module is imported again later, Python reuses the cached module instead of executing its initialization code again.

Module not loadedFirst importexecute initializationCached moduleloaded in memoryLater importreuse cached moduleimportinitialize and cacheimport again
Why does a module's top-level code run on the first import but not on later imports?
Worked example

A State-Holding Module

Imagine a module that creates a counter during its initialization and changes that counter while the program runs. The program imports the module once and later imports the same module again.

  1. 1
    Initial import

    Python locates the module, executes its module-body code, and creates the module's variables and functions.

  2. 2
    Module becomes cached

    After initialization, Python keeps the loaded module in memory.

  3. 3
    State changes

    The module's counter or other state can change while the program executes.

  4. 4
    Second import

    Python returns the cached module instead of re-running its initialization code.

Result

The module's existing state persists because later imports reuse the initialized module.

Using Module Contents

After Python locates and executes a module file, the module's contents become available to the importing program. Those contents can include the module's variables, functions, and other names. The important sequence is locate, load, execute, and then make the contents available. The importing program is therefore using a module that Python has initialized, rather than simply receiving an undeclared collection of names.

In the real world

Consider a program that imports a module containing configuration data. The first import causes Python to locate and initialize that module. Later parts of the program can use the module's contents, and later imports reuse the initialized module rather than creating a newly initialized copy.

Mistakes with Module Search

  • Common mistake

    Assuming Python searches every possible location at the same time.

    What it looks like

    Treating all directories as equally likely sources for a requested module.

    Why it fails

    Python checks directories listed in sys.path in a specific order and stops at the first match.

    Do this instead

    Reason from the ordered entries in sys.path and identify which directory is checked first.

  • Common mistake

    Expecting a second import to run the module's initialization code again.

    What it looks like

    Expecting a module's initial state to be recreated whenever another part of the program imports it.

    Why it fails

    Python caches the module after the first import and reuses it for later imports.

    Do this instead

    Expect later imports to reuse the initialized module and preserve its state during the program's execution.

  • Common mistake

    Assuming an import only retrieves something already present.

    What it looks like

    Thinking of import as equivalent to declaring a variable.

    Why it fails

    An import can trigger a search, file loading, code execution, and exposure of module contents.

    Do this instead

    Trace the complete process: locate the module, load it, execute it, and make its contents available.

  • Common mistake

    Ignoring naming conflicts.

    What it looks like

    Creating a file with the same name as a standard library module.

    Why it fails

    The search order can cause Python to find the local file instead of the intended module.

    Do this instead

    Choose module names carefully and consider the order of directories in sys.path.

Practice the Import Trace

MEDIUM

A program requests a non-built-in module. The requested file is absent from the first directory in sys.path but present in the next directory. Later, the program imports the same module again after the module has changed some internal state. Trace what Python does during the first import and the later import.

Hints
  • Start with the order of directories in sys.path.
  • Ask when the module's code is executed.
  • Ask whether the later import searches for and initializes a new copy or reuses the cached module.
  1. 1
    A strong trace should state that Python checks the first directory, continues because there is no match, and loads the first matching file in the next directory. It should then state that the module's code runs during this first import, the initialized module is cached, and the later import reuses that cached module without re-running its initialization code.

Key Takeaways

  1. 1
    An import statement starts a locate, load, execute, and availability process. For non-built-in modules, Python searches the directories in sys.path in order and stops at the first matching file. A module's initialization code runs only during its first import. Python caches the initialized module, so later imports reuse it and preserve its state. Understanding this process helps explain naming conflicts, import-related errors, and behavior that does not match a re-initialization assumption.

At a glance

  • An import statement tells Python to locate a module, execute its code, and make its contents available.
  • Python searches non-built-in modules through the ordered directories in sys.path.
  • The first matching module file ends the search.
  • Module initialization runs once, after which Python reuses the cached module.
  • Search order and caching explain naming conflicts, persistent module state, and many import-related behaviors.

More concepts starting with C