The import statement is the mechanism that connects your code to every other piece of Python code on your system, including the standard library, third-party packages, and your own modules and packages. To import modules in Python, you use this statement to bring external code into your current namespace. Now that you have learned about modules and packages from the earlier articles in this section, you are ready to understand exactly what happens when Python encounters an import and how to control that process.
When Python encounters an import statement, it performs a consistent sequence of steps. Understanding this sequence turns import errors from frustrating mysteries into solvable problems with clear causes. The same understanding also helps you design your own projects so that other people can import your code without configuration guesswork.
The three-step import process
The first thing Python does when it encounters an import is check the module cache, a dictionary called sys.modules that holds every module that has already been loaded during the current interpreter session. If the requested module name is already a key in that dictionary, Python returns the cached module object immediately and does nothing else. This is why importing the same module twice in the same program does not cause its code to execute twice, and it is why editing a .py file while a program is running does not change the behavior of imports that happened before the edit.
If the module is not in the cache, Python begins a search through a list of directory paths. This list is built at startup from several sources: the directory containing the script that launched the interpreter, directories from environment variables, and the standard library directories that are part of the Python installation. If you have read the overview of Python modules and packages, you already know that this search path is why modules in the same directory as your script are importable without any special configuration. Python iterates through these directories in order and looks for a file with the matching name. The search stops at the first match, and the file it finds becomes the module.
After locating the file, Python creates a new module object and stores it in the cache immediately, before running any of the module's code. This ordering matters: it is what lets two modules import each other without Python looping forever, because the second import finds the first module already in the cache even though that module has not finished executing yet. Python then executes the file's contents from top to bottom, and the function definitions, class definitions, and variable assignments run and populate the module object's namespace. Once execution completes, the module name is bound to that object in the importing code's namespace. From that point forward, every name defined in the module is accessible through dot notation with the module name as the prefix.
A simple import in practice
To see the import process in action, create a module called calculator.py with a few mathematical utility functions. The module contains nothing but function definitions: an add function, a subtract function, a multiply function, and a divide function that raises an error on division by zero. Each function takes two arguments, performs a calculation, and returns the result.
def add(a, b):
return a + b
def multiply(a, b):
return a * b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / bNow create a script in the same directory that imports this module and uses its functions. Every call is prefixed with the module name, which tells anyone reading the file exactly where each function comes from. In a file that imports from five or six different modules, this clarity prevents confusion about which function is being called and where a bug might live.
import calculator
result = calculator.add(12, 8)
print(f"12 + 8 = {result}")
result = calculator.multiply(4, 7)
print(f"4 * 7 = {result}")The module prefix is not extra typing to tolerate; it is documentation. The plain import style is the recommended default for most situations, and you should reach for the from-import style only when the prefix genuinely becomes too verbose to be readable. The article on different ways to import Python modules covers all four import styles and when each one is appropriate.
Where Python looks for your modules
The search path determines which directories Python scans during an import. You can inspect the current search path at any time by printing sys.path from the standard library. On a typical system, the output includes the directory containing the script you ran at the beginning, followed by standard library directories, and finally site-packages directories where third-party libraries are installed. That first entry is not always your terminal's current working directory: if you run python scripts/main.py from a parent folder, sys.path[0] is scripts, not the folder you were standing in when you ran the command. That first entry being the script's own directory is the reason you can create a file in the same folder as your main script and import it by name without any path configuration.
If you need to add a directory to the search path at runtime, you can append to sys.path directly. This is sometimes necessary for one-off scripts or development workflows, but it should not be the permanent solution for a project you share with others. The proper long-term approach is to structure your project as an installable package, which is covered in the article on building a multi-module Python project.
When imports go wrong
The most common import error occurs when Python cannot find the module anywhere on the search path. The fix is usually one of three things: the file is misspelled, the file is in a directory that Python is not searching, or you are running Python from a different working directory than you expect. Print the search path and the current working directory at the top of your script to diagnose this quickly.
A more subtle error happens when a local module shadows a standard library module. If you create a file called json.py in your project directory, any import of json in your project will find your file instead of Python's built-in json module. Your file almost certainly does not provide the same functions, so code that relies on the standard library version will fail with an AttributeError. The fix is to rename your file to something that does not conflict with any standard library module name.
Circular imports are another common pain point. If module A imports from module B and module B also imports from module A, both modules fail to initialize completely because each one needs the other to finish loading first. Python detects this and raises an ImportError with a message about a partially initialized module. The fix involves restructuring either to eliminate the two-way dependency or to move the shared code into a third module that both A and B import unidirectionally. The article on avoiding circular imports in Python covers this pattern in detail with refactoring examples.
Rune AI
Key Insights
- The import statement tells Python to find, execute, and cache a module, making its names available in your program.
- Python searches a list of directories in order, so a local file with the same name as a standard library module will shadow the built-in.
- Once a module is imported, it is cached and will not be re-executed on subsequent imports of the same name.
- Use import module_name as the default style because the module prefix makes it clear where each name comes from.
- Never name your own modules after standard library modules or third-party packages you depend on.
Frequently Asked Questions
Why does Python sometimes say 'No module named X' even when the file exists?
Can I import a module whose name I only know at runtime?
What happens if two modules try to import each other?
Conclusion
The import statement is the gateway to every module in the Python ecosystem. Understanding how it resolves names, where it searches for files, and what happens after a module is loaded gives you the confidence to structure your own projects and to troubleshoot import errors when they appear. Start with simple imports, add the module-name prefix when you call functions, and introduce from-imports only when they genuinely improve readability.
More in this topic
Define and Call Python Functions
Learn the exact syntax for defining and calling Python functions with the def keyword, including naming rules, the function body, and how the call stack works.
Python Function Parameters and Arguments
Learn the difference between parameters and arguments, how to pass values by position and by keyword, and the rules for default parameter values.
Python Functions Explained
Learn what Python functions are, why they matter for organizing code, and how they form the foundation of non-trivial programs.