C.W.K.
Stream
Lesson 02 of 06 · published

pathlib — Object-Oriented Paths

~20 min · pathlib, path, os.path

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

The modern replacement for os.path

pathlib (3.4+) gives you Path objects with methods for every path operation. os.path is functions taking strings; pathlib is methods on objects. The difference matters because path objects compose cleanly (p / "sub" / "file.txt") and read like English (p.exists(), p.is_dir(), p.glob("*.py")).

Path is the main class — and it's smart

Calling Path("some/path") returns an instance of either PosixPath or WindowsPath depending on your OS. Both share the same API. The / operator joins paths regardless of whether the right side has trailing/leading slashes. Path.home() and Path.cwd() are shortcuts you'll use a lot.

The query methods

Every check you'd do via os.path has a Path equivalent: .exists(), .is_file(), .is_dir(), .is_symlink(). They all return bools. .stat() returns the system stat info. .suffix, .stem, .name, .parent, .parts let you split a path without string manipulation.

Reading and writing — the convenience methods

p.read_text(encoding='utf-8') opens, reads, closes. p.write_text(content, encoding='utf-8') opens, writes, closes. p.read_bytes() / p.write_bytes() for binary. These are perfect for small files where you'd otherwise write a four-line with open() block.

Globbing — finding files by pattern

p.glob("*.py") returns all Python files in p. p.rglob("*.py") recurses. Both return iterators of Path objects. Pattern syntax: * any chars, ** any directories, ? single char, [abc] character class.

Pythonic Way: Default to pathlib for new code. Mix with os.path only where you have to — many older libraries and APIs accept strings, and str(Path(...)) bridges them. Don't go halfway: pick pathlib for the path layer and stick with it consistently.

Code

Path basics — construction and the / operator·python
from pathlib import Path

# Construction
p = Path("/Users/you_username")
print(p)                       # /Users/you_username

# Joining — / operator
file_path = p / "Documents" / "notes.txt"
print(file_path)               # /Users/you_username/Documents/notes.txt

# Useful starting points
print(Path.home())             # /Users/you_username
print(Path.cwd())              # current working directory
print(Path(".").resolve())     # current dir, absolute
Query methods — exists / is_file / is_dir / parts·python
from pathlib import Path

p = Path("/etc/hostname")
print(p.exists())             # True
print(p.is_file())            # True
print(p.is_dir())             # False

# Parts and decomposition
q = Path("/etc/profile.d/00-aliases.sh")
print(q.name)                 # '00-aliases.sh'
print(q.stem)                 # '00-aliases'        — name without suffix
print(q.suffix)               # '.sh'
print(q.parent)                # PosixPath('/etc/profile.d')
print(q.parts)                # ('/', 'etc', 'profile.d', '00-aliases.sh')

# Stat
import time
stat = p.stat()
print("size:", stat.st_size)
print("modified:", time.ctime(stat.st_mtime))
read_text / write_text — the shortcuts·python
from pathlib import Path

p = Path("/tmp/note.txt")

# Write
p.write_text("hello pippa\n", encoding="utf-8")

# Read
content = p.read_text(encoding="utf-8")
print(content)

# Bytes
b = Path("/tmp/raw.bin")
b.write_bytes(b"\xDE\xAD\xBE\xEF")
print(b.read_bytes())          # b'\xde\xad\xbe\xef'
glob and rglob — pattern matching·python
from pathlib import Path

# All .py files in a directory (non-recursive)
for py in Path(".").glob("*.py"):
    print(py)

# Recursive — every .py under cwd
for py in Path(".").rglob("*.py"):
    print(py)

# Pattern variations
# *  any chars (no /)
# ** any directories (with rglob)
# ?  single char
# [abc]  any of a, b, c

# Use list() if you want to count or sort
files = sorted(Path(".").glob("*.txt"))
print(f"{len(files)} files")
Common operations — mkdir, rename, unlink·python
from pathlib import Path

# Create a directory (parents=True creates intermediates, exist_ok suppresses errors)
d = Path("/tmp/nested/dir")
d.mkdir(parents=True, exist_ok=True)
print(d.exists())             # True

# Create a file inside it
(d / "child.txt").write_text("hi", encoding="utf-8")

# Rename
original = d / "child.txt"
renamed = original.rename(d / "renamed.txt")
print(renamed)                # /tmp/nested/dir/renamed.txt

# Delete a file
renamed.unlink(missing_ok=True)
print(renamed.exists())       # False

# Remove an empty directory
d.rmdir()                     # raises if not empty
# For non-empty trees: shutil.rmtree(d)

External links

Exercise

Using pathlib: (a) Create a directory /tmp/quest_dir if it doesn't exist. (b) Inside it, write three text files (a.txt, b.txt, c.txt) with different contents. (c) Use glob('*.txt') to iterate them, print the name and the file size. (d) Use rglob from /tmp and verify your three files are found. (e) Clean up — delete the three files and the directory.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.