Find Files and Understand Python Search Paths
Learn how Python finds ordinary files and importable modules using pathlib, os, sys.path, PYTHONPATH, and the current working directory.
Python can “find” two different kinds of things. It can locate an ordinary file on disk, such as report.txt, or it can locate a module or package when an import statement runs. These tasks use related but different mechanisms.
For ordinary filesystem work, use pathlib or os. For imports, Python consults an ordered import search path, exposed mainly through sys.path. Understanding the difference helps explain missing files, ModuleNotFoundError, unexpected imports, and code that behaves differently in an IDE and a terminal.
This lesson assumes that you know how to run Python code and use basic imports, variables, and strings. For background, see Import Modules and What Are Modules.
Two meanings of finding files
Finding an ordinary file
An ordinary file is a filesystem object such as a text file, image, CSV file, or Python source file. To check or enumerate these files, construct a path and use filesystem operations:
Path.exists()checks whether a path exists.Path.is_file()checks whether it identifies a regular file.Path.is_dir()checks whether it identifies a directory.Path.iterdir()lists entries directly inside a directory.Path.glob()matches a pattern in one directory level.Path.rglob()searches recursively below a directory.
Finding an importable module
When Python evaluates import example, it searches for an importable module or package in an ordered collection of locations. This collection is called the import search path, and its main runtime representation is sys.path.
sys.path is not a general-purpose recursive file-search tool. It does not search every folder on a disk. It tells the import system which directories and, in some configurations, archive entries to inspect for a matching module or package. The first suitable match normally wins.
Python's module search path
sys.path is an ordered list of locations Python consults while resolving imports. A location is commonly a directory, but it can also represent an archive or another import-system entry.
Common contributors include:
- The script's location when Python runs a script, or the interactive-session context when Python runs interactively.
- The current working directory in contexts where Python represents it in the search path. An empty string in
sys.pathcommonly means the current working directory. - Directories supplied by the
PYTHONPATHenvironment variable. - Directories containing the standard library, which is the collection of modules distributed with Python.
- Directories containing installed third-party packages, often called
site-packages. - Additional entries added by the runtime or by startup configuration.
The precise entries and their order vary with the operating system, Python installation, virtual environment, launch method, and configuration. A virtual environment is an isolated Python environment with its own interpreter configuration and installed packages.
Inspect sys.path
Inspect the active process instead of guessing which interpreter or environment is being used:
import sys
for location in sys.path:
print(location)
You may also print one location per line with:
import sys
print("\n".join(sys.path))
Typical output can contain:
- An empty string, commonly representing the current working directory.
- A project or script directory.
- A standard-library directory inside the Python installation.
- A virtual environment's
site-packagesdirectory. - A system-wide
site-packagesdirectory. - A ZIP archive entry or another configured import location.
Inspect this list when an import succeeds unexpectedly, fails with ModuleNotFoundError, or appears to use the wrong version of a package. Also verify which interpreter is running the program, because two Python installations can have different search paths.
Add an import location at runtime
You can append a directory for the lifetime of the running process:
import sys
extra_directory = r"E:\backup"
sys.path.append(extra_directory)
for location in sys.path:
print(location)
append() places the directory at the end. Therefore, if an earlier location already contains a module with the requested name, that earlier module takes precedence.
When higher precedence is intentional, insert the directory at the beginning:
import sys
extra_directory = r"E:\backup"
sys.path.insert(0, extra_directory)
Using insert(0, ...) can deliberately select a local implementation, but it can also cause surprising imports. Runtime edits can hide packaging, dependency, or environment problems. For maintainable applications, prefer installing the project, using a virtual environment, defining a clear package structure, and configuring dependencies explicitly. See Import Modules for related import concepts.
Configure imports with PYTHONPATH
PYTHONPATH is an environment variable that supplies additional import locations before Python starts. It can be useful for temporary development, shared internal code, or a controlled execution environment.
On Windows, separate multiple locations with a semicolon:
set PYTHONPATH=C:\path\to\project;C:\path\to\shared
On macOS and Linux, separate them with a colon:
export PYTHONPATH=/path/to/project:/path/to/shared
PYTHONPATH affects many Python processes launched from that environment. It can make one project import another project's code, select an incompatible package version, or behave differently on another computer. Avoid relying on it for distributable applications unless the environment is deliberately controlled.
Understand the current working directory
The current working directory (CWD) is the process directory used as the base for relative filesystem paths. An absolute path identifies a location independently of the CWD. A relative path is interpreted from a current or otherwise specified base.
Read the CWD with either API:
from pathlib import Path
import os
print(Path.cwd())
print(os.getcwd())
Change it with os.chdir():
from pathlib import Path
import os
print(Path.cwd())
os.chdir(r"C:\Python\Scripts")
print(Path.cwd())
Changing the CWD affects all later relative filesystem operations in the running process, including paths opened by other functions. It does not change the directory permanently for the parent terminal, and it is not generally a good substitute for deliberate path configuration.
A program's CWD may differ depending on whether it was launched from a terminal, IDE, scheduler, service, or another program. That is why a relative path can work in a terminal but fail in an IDE or scheduled task.
Construct paths safely
pathlib.Path is the preferred cross-platform path API. The division operator joins path components using the correct platform convention:
from pathlib import Path
project = Path.home() / "projects" / "inventory"
config = project / "config" / "settings.json"
print(config)
When writing a Windows path directly in a Python string, use a raw string, doubled backslashes, or forward slashes:
from pathlib import Path
one = Path(r"C:\Users\Ada\project")
two = Path("C:\\Users\\Ada\\project")
three = Path("C:/Users/Ada/project")
Ordinary backslashes introduce escape sequences. For example, \n means a newline, \t means a tab, and \U begins a Unicode escape. These sequences can change the path or produce a syntax error. Raw strings avoid most of these interpretations, but a raw string cannot end with a single backslash.
Avoid hard-coding an interpreter installation directory such as a particular Python version's folder. Use a project-relative path, Path.home(), a command-line argument, or application configuration when that better represents the intended location.
Find ordinary files on disk
Check a known path
from pathlib import Path
report = Path.home() / "Documents" / "report.txt"
print(report.exists())
print(report.is_file())
print(report.is_dir())
exists() answers whether the path exists. is_file() and is_dir() distinguish the type of object. A path can exist without being a regular file, so use is_file() when a file is required.
List one directory
from pathlib import Path
folder = Path("data")
for entry in folder.iterdir():
if entry.is_file():
print(entry)
Use a glob pattern to filter entries:
from pathlib import Path
for csv_file in Path("data").glob("*.csv"):
if csv_file.is_file():
print(csv_file)
A glob is a filename pattern mechanism. For example, *.csv matches names ending in .csv. You can also filter explicitly by suffix or filename:
from pathlib import Path
for entry in Path("data").iterdir():
if entry.is_file() and entry.suffix.lower() == ".csv":
print(entry.name)
Search recursively
from pathlib import Path
project = Path(".")
for file_path in project.rglob("*.py"):
if file_path.is_file():
print(file_path)
rglob() walks the directory tree below the starting path. Recursive searches can be slow on large trees, especially when they include caches, dependency directories, network locations, or many small files. Start with a narrow root and a specific pattern.
Searches can encounter permission errors, inaccessible locations, symbolic links, special files, or files that disappear while the search is running. Handle filesystem exceptions when the search must continue reliably, and decide whether links should be followed. Do not assume that every directory is readable.
Verify what Python imported
When an import succeeds but the code is not the code you expected, inspect the module's __file__ attribute:
import json
print(getattr(json, "__file__", "no filesystem source path"))
This identifies the source location when the module has a normal filesystem source. Built-in modules, frozen modules, and some namespace-based modules may not provide a normal file path.
This technique diagnoses module shadowing: an unintended import of a local or earlier-path module with the same name as another module. For example, a project file named json.py, random.py, or requests.py can be selected before the intended library.
import sys
import json
print("Imported from:", getattr(json, "__file__", "no file"))
print("Search order:")
for location in sys.path:
print(" ", location)
If an interactive session already imported the wrong module, restart the interpreter after renaming the conflicting file. Removing or reordering an entry in sys.path does not necessarily unload a module already stored in sys.modules.
Troubleshoot common path problems
ModuleNotFoundError despite an existing file
- Print
sys.pathand check whether the directory containing the module is present. - Print
Path.cwd(); the program may have started in a different directory. - Confirm the filename, spelling, and capitalization.
- Check that the target is structured as an importable module or package.
- Confirm the interpreter executable and active virtual environment. Installing a package into one environment does not install it into every environment.
The wrong module is imported
- Print
module.__file__when available. - Inspect the order of
sys.path. - Look for a local file that has the same name as a standard-library or third-party module.
- Rename the conflicting file and restart the interpreter.
A Windows path is invalid
- Use
Pathinstead of manually concatenating strings. - Use a raw string, doubled backslashes, or forward slashes.
- Check for escape sequences such as
\n,\t, and\U.
Relative access differs between launch methods
Print Path.cwd() at startup. Then use a deliberate base path, pass a path through configuration or command-line arguments, or configure the IDE, scheduler, or service's working directory explicitly. A relative path should not depend accidentally on the tool that launched the program.
Recursive search is slow or stops partway through
- Reduce the search root.
- Use a specific pattern such as
*.csvrather than searching every name. - Handle permission-related exceptions where appropriate.
- Consider symbolic links, special directories, and the cost of scanning a large tree.
Practical checklist
- Decide whether you need a filesystem search or an import search.
- For ordinary files, start with
pathlib.Path. - For imports, inspect
sys.pathand verify the active interpreter. - Print
Path.cwd()whenever relative paths behave unexpectedly. - Use
module.__file__to verify which module was selected. - Prefer packaging, virtual environments, and explicit configuration over permanent
sys.pathorPYTHONPATHhacks.
For file-reading operations after locating a path, continue with How To Read And Write Files. For command-line-controlled paths, see Command Line.