Home› Tutorials› Standard Library
Standard Librarypathlib in Python: File Paths as Objects, Not Strings
Pathobjects know their parts —.name,.stem,.suffix,.parent— without string splitting.- The
/operator joins path segments cross-platform:base / "subdir" / "file.txt". path.read_text()andpath.write_text()handle open/close automatically.path.glob("*.py")andpath.rglob("**/*.py")return lazy iterators of matching paths.
Importing pathlib
pathlib has been part of the standard library since Python 3.4. You rarely need the full module namespace — importing Path directly covers almost every use case:
from pathlib import Path
# On all platforms, Path() gives you the right concrete type
p = Path(".")
print(type(p)) # PosixPath on Linux/macOS, WindowsPath on Windows
The concrete PosixPath and WindowsPath types are selected automatically. You write code against the Path interface and it behaves correctly on every operating system.
Q: How do I create a Path object?
Pass a string, another Path, or any combination to the Path() constructor. Multiple arguments are joined automatically:
from pathlib import Path
home = Path("/home/user")
config = Path("/home/user/.config/app/settings.toml")
# Path parts are accessible as attributes
print(config.name) # settings.toml
print(config.stem) # settings
print(config.suffix) # .toml
print(config.parent) # /home/user/.config/app
# Absolute vs relative
rel = Path("reports/2026/q1.csv")
print(rel.parts) # ('reports', '2026', 'q1.csv')
These attributes eliminate the need for os.path.basename(), os.path.splitext(), and os.path.dirname() entirely.
Q: How do I build paths without string concatenation?
The / operator is overloaded on Path objects to join segments. This is the idiomatic way to construct new paths and it works identically on all platforms — no need to choose between / and \:
from pathlib import Path
base = Path("/var/data")
logs = base / "logs"
report = base / "output" / "report.csv"
archive = base / "archive" / "2026" / "january.tar.gz"
print(report) # /var/data/output/report.csv
print(archive) # /var/data/archive/2026/january.tar.gz
# Resolve the current script's directory
here = Path(__file__).parent
sibling = here / "utils.py"
You can also call path.resolve() to get the absolute, canonical path with all symlinks resolved, and path.relative_to(base) to express a path relative to a given ancestor.
Q: How do I read and write files with pathlib?
Path objects have high-level convenience methods that open, read or write, and close the file in a single call:
from pathlib import Path
p = Path("notes.txt")
# Write text (creates or overwrites)
p.write_text("Hello, pathlib!\n", encoding="utf-8")
# Read text back
content = p.read_text(encoding="utf-8")
print(content) # Hello, pathlib!
# Binary equivalents
p.write_bytes(b"\x89PNG\r\n\x1a\n")
data = p.read_bytes()
# Create parent directories if missing
output = Path("build/dist/bundle.js")
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text("console.log('hello');")
For larger files where you want to stream content line by line, use p.open() as a context manager — it returns a standard file object exactly like the built-in open().
Q: How do I find files matching a pattern?
path.glob(pattern) searches the immediate directory and yields matching Path objects. path.rglob(pattern) descends recursively:
from pathlib import Path
project = Path(".")
# All Python files in the current directory only
for f in project.glob("*.py"):
print(f.name)
# All Python files anywhere under the project
for f in project.rglob("*.py"):
print(f)
# Collect into a sorted list
py_files = sorted(project.rglob("*.py"))
print(f"Found {len(py_files)} Python files")
# Multiple extensions using a set comprehension
sources = {p for p in project.rglob("*") if p.suffix in {".py", ".pyi"}}
Both methods return lazy generators — they do not load all results into memory. Use list() or sorted() only when you need random access or a stable ordering.
Q: How do I check whether a path exists or get file metadata?
Path exposes existence and stat information as simple method calls:
from pathlib import Path
p = Path("data/users.csv")
print(p.exists()) # True or False
print(p.is_file()) # True only if it exists and is a regular file
print(p.is_dir()) # True only if it exists and is a directory
# File size and timestamps via os.stat_result
stat = p.stat()
print(stat.st_size) # size in bytes
print(stat.st_mtime) # last-modified time as a Unix timestamp
# Safe operations
p.unlink(missing_ok=True) # delete; no error if already gone
p.parent.mkdir(parents=True, exist_ok=True) # create directory tree
Q: Should I still use os.path?
For new code, prefer pathlib. The object-oriented interface is more readable and the methods cover the full lifecycle of a path without importing multiple modules. That said, os.path is not going anywhere, and many third-party libraries still accept strings. You can convert a Path to a string at the boundary with str(p) or pass it directly — most standard-library functions accept Path objects natively since Python 3.6.
| Task | os.path style | pathlib style |
|---|---|---|
| Join segments | os.path.join(a, b) | a / b |
| Get filename | os.path.basename(p) | p.name |
| Get extension | os.path.splitext(p)[1] | p.suffix |
| Get parent dir | os.path.dirname(p) | p.parent |
| Check existence | os.path.exists(p) | p.exists() |
| Read all text | open(p).read() | p.read_text() |
/ operator on Path objects guarantees the correct separator for the running operating system, and Path.resolve() handles drive letters and UNC paths on Windows without any conditional logic in your code.