
When you import a module in Python, the interpreter doesn’t just blindly search every folder on your computer. Instead, it follows a well-defined order to locate the module you’re asking for. This order is crucial because it determines which version of a module you actually get, especially when multiple copies exist.
At the heart of this process is the sys.path list. Think of it as a prioritized list of directories that Python scans, one by one, looking for the module you requested. The search stops the moment it finds a matching file or package.
Here’s the typical order Python follows:
- The directory containing the input script (or the current directory if running interactively).
- Directories listed in the
PYTHONPATHenvironment variable, if set. - Standard library directories.
- Site-packages directories for third-party modules.
But don’t just take my word for it-let’s peek inside sys.path and see what it looks like in a live Python session:
import sys
for i, path in enumerate(sys.path):
print(f"{i}: {path}")
Notice the very first entry is typically an empty string '', which represents the current directory. That means if you run a script from /home/joel/projects, Python starts looking for modules right there.
Now, if you put a file named utils.py in that directory, it will be imported before any globally installed package named utils. This behavior can be a blessing or a curse, depending on what you want.
One subtlety that trips up new Pythonistas is how packages and modules are resolved differently. For example, if you have a folder called mypkg with an __init__.py, Python treats it as a package. When you do import mypkg.submodule, Python looks inside mypkg for submodule.py or a submodule folder with its own __init__.py.
Another point is the order of sys.path. Since it’s a list, you can manipulate it, but the first matching module found in the list wins. This means if you add paths at the end, they have lower priority. Conversely, inserting at the front bumps their priority.
Here’s a quick illustration:
import sys
print("Before modification:")
print(sys.path[0])
sys.path.insert(0, '/home/joel/custom_modules')
print("After modification:")
print(sys.path[0])
By inserting /home/joel/custom_modules at the start, you ensure Python looks there before anywhere else. This is handy when you want to override a standard module temporarily or test a new version.
One thing to keep in mind is that Python caches imported modules in sys.modules. So if you import utils once, and then change your sys.path to point somewhere else with a different utils.py, the original module remains loaded until you explicitly reload or restart your interpreter.
To see how PYTHONPATH fits in, imagine you set it like this in your shell:
export PYTHONPATH=/home/joel/devlibs:/opt/extra_python
When Python starts, it appends these directories to sys.path, right after the script directory. That means you can globally add custom libraries without touching your code.
Finally, remember that built-in modules like sys or os are always found first because they’re compiled into the interpreter. They don’t rely on sys.path at all.
