When you pass a bare filename like "data.csv" to the open function, Python looks for that file in the current working directory. This works for simple scripts that live in the same folder as their data, but real projects quickly outgrow that assumption. Your script might live in a src folder while the data lives in a data folder. You might need to read a configuration file from the user's home directory, process every file in a nested folder tree, or generate output paths that work on Windows, macOS, and Linux without manual changes. Python file paths are the mechanism that tells open exactly where on disk a file lives, and Python provides two robust APIs for constructing and manipulating them: the older os.path module and the modern pathlib module introduced in Python 3.4.
Before you can open a file, you need a path that accurately describes its location. A path that works on your development machine but breaks on a colleague's computer or a server is a path that was not constructed properly. This article covers the two path APIs, explains when to use each one, and gives you patterns that produce correct paths regardless of which operating system runs your code. If you have read the articles on opening and closing files in Python and file modes in Python, you already know how to open files once you have a path. This article completes the picture by showing you how to build that path reliably.
Absolute paths and relative paths
An absolute path describes a file's location starting from the root of the file system. On macOS and Linux, the root is a single forward slash, and an absolute path looks like /home/alex/projects/data/input.csv. On Windows, the root is a drive letter like C:, and an absolute path looks like C:\Users\alex\projects\data\input.csv. Absolute paths are unambiguous: no matter what the current working directory is, the same absolute path always points to the same file on the same machine. The tradeoff is that absolute paths are not portable. A path that starts with /home/alex does not exist on a machine where the user is named jordan, and a path with C:\ does not exist on macOS or Linux at all.
A relative path describes a file's location relative to the current working directory. If the current working directory is /home/alex/projects, the relative path data/input.csv points to /home/alex/projects/data/input.csv. A relative path that starts with two dots moves up one directory level: ../config/settings.json from /home/alex/projects points to /home/alex/config/settings.json. Relative paths are portable because they do not embed machine-specific details like usernames or drive letters. As long as your project maintains a consistent internal folder structure, relative paths from the project root work on any machine.
from pathlib import Path
cwd = Path.cwd()
print(f"Current directory: {cwd}")
relative = Path("data/input.csv")
absolute = relative.resolve()
print(f"Absolute path: {absolute}")The Path.cwd method returns the current working directory as a pathlib Path object. The resolve method converts a relative path to an absolute one by prepending the current working directory and resolving any dot and dot-dot segments. This is the standard pattern for turning a project-relative path into a full path that open can use, and it works identically on Windows, macOS, and Linux because pathlib handles the platform-specific separator character automatically.
Building paths with pathlib
The pathlib module represents file system paths as objects rather than strings. You create a Path object by passing a string to the Path constructor, and you build longer paths by using the forward slash operator between Path objects and strings. This operator overloading is one of the most elegant features of pathlib: it reads like natural path construction and automatically inserts the correct separator for the current operating system.
from pathlib import Path
project = Path("my_project")
data_dir = project / "data"
input_file = data_dir / "input.csv"
print(input_file)
print(input_file.exists())
print(input_file.is_file())The expression project / "data" joins the Path object and the string with the platform-appropriate separator, producing my_project/data on macOS and Linux or my_project\data on Windows. The exists method returns True if the path points to something on disk, and is_file returns True specifically if it is a regular file rather than a directory. These methods let you check whether a file is present before attempting to open it, which avoids FileNotFoundError when the file is legitimately optional.
Pathlib also provides convenient shortcuts for common locations. Path.home returns the current user's home directory regardless of platform, and Path.cwd returns the current working directory. The iterdir method lists the contents of a directory, and the glob method finds files matching a pattern, such as all CSV files in a folder. These features make pathlib suitable not just for constructing individual file paths but for traversing directory trees, filtering files by extension, and building file processing pipelines.
Working with os.path in existing code
The os.path module predates pathlib and remains widely used, especially in codebases that predate Python 3.4 or that need to run on older Python versions. It represents paths as plain strings and provides functions that operate on those strings. The os.path.join function is the equivalent of pathlib's forward slash operator: it joins path components with the correct separator for the current platform.
The os.path module also includes functions for splitting a path into its directory and filename components, extracting the file extension, checking whether a path is absolute, and normalizing a path by collapsing redundant separators and dot-dot segments. The os.path.abspath function converts a relative path to an absolute one, and os.path.exists checks whether a path points to something on disk. These functions work reliably and are well-tested, so there is no urgency to rewrite os.path code into pathlib. But for new code, pathlib is the recommended choice because the object-oriented style produces fewer bugs from accidentally treating a path as a plain string.
import os
data_dir = os.path.join("my_project", "data")
input_file = os.path.join(data_dir, "input.csv")
print(os.path.abspath(input_file))This code does the same thing as the pathlib example above, but the function-call style is more verbose and the intermediate values are plain strings rather than Path objects. Both approaches produce a correct path for the current platform. The choice between them is primarily a matter of codebase consistency and personal preference, with pathlib being the forward-looking option.
Path safety and cross-platform concerns
The most common path-related bug in Python programs is hardcoding a separator character. Writing "data\input.csv" with a backslash works on Windows but produces a filename with a literal backslash in it on macOS and Linux. Writing "data/input.csv" with a forward slash works on macOS and Linux and, surprisingly, also works on modern Windows when passed to Python's open function directly, but it fails when passed to command-line tools or other Windows APIs that expect backslashes. The safest approach is to never type a separator character at all and instead let pathlib or os.path.join construct the path.
Another common issue is assuming the current working directory. When you double-click a Python script in a file manager, the current working directory might be the user's home directory rather than the script's directory. When you run a script from a different folder, relative paths resolve relative to that folder, not the script's location. If your script depends on files in its own directory, use the file variable combined with Path to construct paths relative to the script rather than the working directory. The expression Path(file).parent gives you the directory containing the script, and you can build all other project paths from that anchor.
Path validation is the final piece of path safety. Before passing a path to open, especially a path that came from user input or a configuration file, check whether it exists and whether it is the right type. The resolve method turns relative paths into absolute ones and normalizes dot-dot segments, which prevents directory traversal attacks where a malicious relative path like ../../../etc/passwd escapes the intended directory. For writing new files, ensure the parent directory exists by calling mkdir with the parents and exist_ok arguments on the parent Path before opening the file for writing. These checks add a few lines of code but prevent entire classes of bugs where your program writes to the wrong location or fails silently because a directory does not exist yet.
Rune AI
Key Insights
- pathlib is the modern, recommended way to work with file paths in Python, providing an object-oriented API.
- Use Path.cwd() to get the current working directory and Path.home() for the user's home directory.
- Join path components with the / operator in pathlib or os.path.join() in the older API.
- Use resolve() to convert relative paths to absolute paths and .exists() to check if a path points to a real file.
- Never hardcode forward slashes or backslashes; let pathlib or os.path handle the platform separator.
Frequently Asked Questions
What is the difference between os.path and pathlib?
How do I get the current working directory in Python?
What is the difference between an absolute path and a relative path?
Conclusion
Working with file paths correctly is the difference between a script that runs on your machine and one that runs on anyone's machine. pathlib gives you an object-oriented API that handles path separators, joins, and resolution automatically. For new code, prefer pathlib over os.path. For existing codebases, os.path remains reliable and well-understood. Either way, construct paths programmatically rather than hardcoding separators, and test your path logic on more than one operating system.
More in this topic
Create Custom Iterators in Python
Learn how to build your own iterators in Python by implementing the __iter__() and __next__() methods, with practical examples and reusable patterns.
Python Iterator Protocol Explained
Learn how Python's iterator protocol works with the __iter__() and __next__() methods, and understand the contract every iterator must follow.
Python Iterables and Iterators Explained
Learn what iterables and iterators are in Python, how they power for loops under the hood, and why understanding them unlocks cleaner data processing patterns.