Python

Python Pathlib: File System Modern

Tutorial lengkap Python pathlib โ€” Path objects, operasi file system, globbing, pattern matching, dan best practices untuk manipulasi file modern dengan contoh kode praktis

1. Pengenalan Pathlib

pathlib adalah modul standar Python yang diperkenalkan di Python 3.4 untuk menyediakan cara modern dan berorientasi objek dalam bekerja dengan file system. Modul ini menggantikan pendekatan lama menggunakan os.path dengan pendekatan yang lebih elegan dan Pythonic.

Sebelum pathlib, programmer Python harus mengandalkan fungsi-fungsi dari modul os dan os.path yang menggunakan string untuk merepresentasikan path. Pendekatan ini sering kali menghasilkan kode yang sulit dibaca dan rentan terhadap kesalahan.

Mengapa Menggunakan Pathlib?

Keunggulan Penjelasan
OOP ApproachPath direpresentasikan sebagai objek, bukan string biasa
Cross-PlatformOtomatis menangani perbedaan separator path Windows dan Unix
Method ChainingBisa menggabungkan beberapa operasi dalam satu baris
Built-in OperationsFile read/write, globbing, dan operasi directory tersedia langsung
Readable CodeKode lebih mudah dibaca dan dipahami dibanding os.path
Type SafetyMenggunakan objek Path, bukan string yang bisa salah format

Perbandingan: os.path vs pathlib

Python โ€” os.path vs pathlib
# ===== CARA LAMA: os.path =====
import os

base_dir = os.path.expanduser("~")
project_dir = os.path.join(base_dir, "projects", "myapp")
config_file = os.path.join(project_dir, "config.json")

if os.path.exists(config_file):
    with open(config_file, 'r') as f:
        content = f.read()

# Mendapatkan ekstensi file
ext = os.path.splitext(config_file)[1]  # '.json'

# Mendapatkan nama file tanpa ekstensi
name = os.path.splitext(os.path.basename(config_file))[0]  # 'config'

# ===== CARA BARU: pathlib =====
from pathlib import Path

base_dir = Path.home()
project_dir = base_dir / "projects" / "myapp"
config_file = project_dir / "config.json"

if config_file.exists():
    content = config_file.read_text()

# Mendapatkan ekstensi file
ext = config_file.suffix  # '.json'

# Mendapatkan nama file tanpa ekstensi
name = config_file.stem  # 'config'
Diagram: Struktur Path Object
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              STRUKTUR PATH OBJECT                    โ”‚
โ”‚                                                      โ”‚
โ”‚  /home/user/projects/myapp/config.json               โ”‚
โ”‚  โ”œโ”€โ”€ anchor: "/"                                     โ”‚
โ”‚  โ”œโ”€โ”€ root: "/"                                       โ”‚
โ”‚  โ”œโ”€โ”€ parent: /home/user/projects/myapp               โ”‚
โ”‚  โ”œโ”€โ”€ parents: [/home/user/projects,                  โ”‚
โ”‚  โ”‚            /home/user, /home, /]                  โ”‚
โ”‚  โ”œโ”€โ”€ name: "config.json"                             โ”‚
โ”‚  โ”œโ”€โ”€ stem: "config"                                  โ”‚
โ”‚  โ”œโ”€โ”€ suffix: ".json"                                 โ”‚
โ”‚  โ”œโ”€โ”€ suffixes: [".json"]                             โ”‚
โ”‚  โ”œโ”€โ”€ parts: ("/", "home", "user", "projects",        โ”‚
โ”‚  โ”‚          "myapp", "config.json")                  โ”‚
โ”‚  โ””โ”€โ”€ as_posix(): "home/user/projects/myapp/config.json"โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
๐Ÿ’ก Tips

Pathlib tersedia secara bawaan di Python 3.4+. Tidak perlu instalasi tambahan. Untuk Python versi lama, gunakan pip install pathlib2.

2. Membuat Path Objects

Ada beberapa cara untuk membuat objek Path di Python. Modul pathlib menyediakan dua kelas utama: Path (untuk sistem operasi saat ini) dan PurePath (untuk operasi yang tidak memerlukan akses file system).

Path dan PurePath

Python โ€” Membuat Path
from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath

# Menggunakan Path (otomatis sesuai OS saat ini)
p1 = Path("documents/report.pdf")
p2 = Path.home() / "documents" / "report.pdf"
p3 = Path.cwd() / "data" / "input.csv"

# Menggunakan PurePath (tanpa akses file system)
pp1 = PurePath("folder/subfolder/file.txt")
pp2 = PurePosixPath("/home/user/file.txt")    # Format Unix
pp3 = PureWindowsPath("C:\\Users\\user\\file.txt")  # Format Windows

# Dari string yang sudah ada
path_str = "/home/user/data/file.csv"
p4 = Path(path_str)

# Dari beberapa bagian
p5 = Path("/home", "user", "data", "file.csv")

# Dari tuple
parts = ("/home", "user", "data", "file.csv")
p6 = Path(*parts)

print(type(p1))   # <class 'pathlib.PosixPath'> (Linux/Mac)
print(type(p1))   # <class 'pathlib.WindowsPath'> (Windows)

# PurePath bisa digunakan lintas platform
# Ini berguna saat menulis kode yang memproses path dari OS lain
print(PurePosixPath("/usr/bin/python3"))    # Selalu format Unix
print(PureWindowsPath("C:/Python312/python.exe"))  # Selalu format Windows

Path Khusus

Python โ€” Special Paths
from pathlib import Path

# Path direktori home user
home = Path.home()
print(home)  # /home/user (Linux) atau C:\Users\user (Windows)

# Path direktori kerja saat ini
cwd = Path.cwd()
print(cwd)  # /home/user/projects

# Path absolut dari path relatif
rel_path = Path("data/input.csv")
abs_path = rel_path.resolve()
print(abs_path)  # /home/user/projects/data/input.csv

# Membuat Path dari ekspansi tilde (~)
tilde_path = Path("~/documents/file.txt")
expanded = tilde_path.expanduser()
print(expanded)  # /home/user/documents/file.txt

# Menggunakan operator /
# Operator / bisa digunakan untuk menggabungkan path
project = Path("/home/user/projects/myapp")
src = project / "src"
main = src / "main.py"
print(main)  # /home/user/projects/myapp/src/main.py

# Menggabungkan dengan path relatif
config = project / "config" / "settings.json"
print(config)

# Chain beberapa level sekaligus
deep_path = Path.home() / "projects" / "web" / "src" / "components" / "App.tsx"
print(deep_path)

Konversi Path

Python โ€” Konversi
from pathlib import Path

p = Path("/home/user/documents/report.pdf")

# Konversi ke string
path_str = str(p)
print(type(path_str))  # <class 'str'>

# Konversi ke bytes
path_bytes = bytes(p)
print(type(path_bytes))  # <class 'bytes'>

# Format POSIX (menggunakan /)
posix = p.as_posix()
print(posix)  # /home/user/documents/report.pdf

# Format URI
uri = p.as_uri()
print(uri)  # file:///home/user/documents/report.pdf

# Hanya untuk path absolut
try:
    Path("relative/path").as_uri()
except ValueError as e:
    print(f"Error: {e}")  # relative path can't be expressed as a file URI

# Menggunakan f-string
p = Path("data") / "output" / "results.json"
print(f"File: {p}")        # File: data/output/results.json
print(f"Exists: {p.exists()}")  # Exists: False

3. Properti dan Atribut Path

Objek Path memiliki banyak properti yang memudahkan kita untuk mengakses berbagai komponen dari sebuah path tanpa perlu memanipulasi string secara manual.

Komponen Dasar Path

Python โ€” Properti Path
from pathlib import Path

p = Path("/home/user/projects/myapp/src/main.py")

# Nama file lengkap (termasuk ekstensi)
print(f"name: {p.name}")          # main.py

# Nama tanpa ekstensi (stem)
print(f"stem: {p.stem}")          # main

# Ekstensi file
print(f"suffix: {p.suffix}")      # .py

# Semua ekstensi (untuk file seperti archive.tar.gz)
p2 = Path("archive.tar.gz")
print(f"suffixes: {p2.suffixes}")  # ['.tar', '.gz']
print(f"stem: {p2.stem}")          # archive.tar
print(f"suffix: {p2.suffix}")      # .gz

# Mengganti nama file
new_name = p.with_name("test.py")
print(f"with_name: {new_name}")    # /home/user/projects/myapp/src/test.py

# Mengganti stem
new_stem = p.with_stem("utils")
print(f"with_stem: {new_stem}")    # /home/user/projects/myapp/src/utils.py

# Mengganti ekstensi
new_suffix = p.with_suffix(".txt")
print(f"with_suffix: {new_suffix}") # /home/user/projects/myapp/src/main.txt

# Bagian-bagian path (parts)
print(f"parts: {p.parts}")
# ('/', 'home', 'user', 'projects', 'myapp', 'src', 'main.py')

Parent dan Ancestors

Python โ€” Parent Paths
from pathlib import Path

p = Path("/home/user/projects/myapp/src/main.py")

# Parent directory (satu level ke atas)
print(f"parent: {p.parent}")
# /home/user/projects/myapp/src

# Semua parent (dari terdekat ke terjauh)
print(f"parents: {list(p.parents)}")
# [PosixPath('/home/user/projects/myapp/src'),
#  PosixPath('/home/user/projects/myapp'),
#  PosixPath('/home/user/projects'),
#  PosixPath('/home/user'),
#  PosixPath('/home'),
#  PosixPath('/')]

# Mengakses parent tertentu dengan index
print(f"parents[0]: {p.parents[0]}")  # /home/user/projects/myapp/src
print(f"parents[1]: {p.parents[1]}")  # /home/user/projects/myapp
print(f"parents[2]: {p.parents[2]}")  # /home/user/projects

# Menggunakan operator /
# Naik ke parent lalu turun ke path lain
sibling = p.parent / "utils.py"
print(f"sibling: {sibling}")
# /home/user/projects/myapp/src/utils.py

# Naik 2 level dan turun ke folder lain
tests = p.parents[1] / "tests" / "test_main.py"
print(f"tests: {tests}")
# /home/user/projects/myapp/tests/test_main.py

# Relative path dari satu path ke path lain
base = Path("/home/user/projects")
target = Path("/home/user/projects/myapp/src/main.py")
rel = target.relative_to(base)
print(f"relative: {rel}")  # myapp/src/main.py

Path Absolut dan Relatif

Python โ€” Absolute vs Relative
from pathlib import Path

# Mengecek apakah path absolut
print(Path("/home/user").is_absolute())   # True
print(Path("relative/path").is_absolute())  # False

# Mengubah ke path absolut
rel = Path("data/output.csv")
abs_path = rel.resolve()
print(f"Absolute: {abs_path}")

# Mendapatkan path relatif
cwd = Path.cwd()
target = Path.home() / "documents" / "file.txt"
try:
    rel_path = target.relative_to(cwd)
    print(f"Relative: {rel_path}")
except ValueError:
    print("Tidak bisa membuat path relatif (berbeda root)")

# is_relative_to (Python 3.9+)
target = Path("/home/user/projects/myapp/main.py")
print(target.is_relative_to("/home/user/projects"))  # True
print(target.is_relative_to("/tmp"))  # False

# Anchor (root + drive)
p_win = Path("C:/Users/user/file.txt")
# print(p_win.anchor)  # 'C:\\' di Windows

p_unix = Path("/home/user/file.txt")
print(p_unix.anchor)  # '/'

4. Membaca dan Menulis File

Salah satu keunggulan terbesar pathlib adalah kemampuannya untuk membaca dan menulis file secara langsung dari objek Path tanpa perlu menggunakan open().

Membaca File

Python โ€” Read File
from pathlib import Path

config_path = Path("config.json")

# Membaca sebagai teks (satu baris!)
if config_path.exists():
    content = config_path.read_text(encoding="utf-8")
    print(content)

# Membaca sebagai binary
image_path = Path("images/logo.png")
if image_path.exists():
    data = image_path.read_bytes()
    print(f"Size: {len(data)} bytes")

# Membaca per baris
lines_path = Path("data.csv")
if lines_path.exists():
    # read_text() lalu split
    lines = lines_path.read_text().splitlines()
    for line in lines[:5]:  # 5 baris pertama
        print(line)

# Membaca dengan open() untuk kontrol lebih
log_path = Path("app.log")
if log_path.exists():
    with log_path.open("r", encoding="utf-8") as f:
        for line in f:
            if "ERROR" in line:
                print(line.strip())

# Context manager dari pathlib
with Path("data.txt").open("r") as f:
    content = f.read()

Menulis File

Python โ€” Write File
from pathlib import Path
import json

output_dir = Path("output")
output_dir.mkdir(parents=True, exist_ok=True)

# Menulis teks (satu baris!)
text_file = output_dir / "report.txt"
text_file.write_text("Laporan harian\nTanggal: 26 Juni 2026\n", encoding="utf-8")

# Menulis binary
binary_file = output_dir / "data.bin"
binary_file.write_bytes(b'\x00\x01\x02\x03\x04')

# Menulis JSON
config = {
    "app_name": "MyApp",
    "version": "1.0.0",
    "debug": False,
    "database": {
        "host": "localhost",
        "port": 5432
    }
}
config_path = output_dir / "config.json"
config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")

# Menggunakan open() untuk menulis dengan kontrol lebih
log_path = output_dir / "app.log"
with log_path.open("a", encoding="utf-8") as f:  # mode append
    f.write("[2026-06-26] Aplikasi dimulai\n")
    f.write("[2026-06-26] Konfigurasi dimuat\n")

# Menulis beberapa baris sekaligus
lines = ["Baris 1", "Baris 2", "Baris 3", "Baris 4"]
multi_path = output_dir / "multi.txt"
multi_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
โš ๏ธ Peringatan

write_text() dan write_bytes() akan menimpa file yang sudah ada. Gunakan mode "a" (append) via open() jika ingin menambahkan di akhir file.

Pathlib menyediakan berbagai method untuk menavigasi dan menjelajahi struktur direktori.

List Directory

Python โ€” List Directory
from pathlib import Path

project_dir = Path(".")

# List semua item di direktori
print("=== Semua Item ===")
for item in project_dir.iterdir():
    print(f"  {'๐Ÿ“' if item.is_dir() else '๐Ÿ“„'} {item.name}")

# Hanya file
print("\n=== Hanya File ===")
files = [f for f in project_dir.iterdir() if f.is_file()]
for f in files:
    print(f"  ๐Ÿ“„ {f.name} ({f.stat().st_size} bytes)")

# Hanya direktori
print("\n=== Hanya Direktori ===")
dirs = [d for d in project_dir.iterdir() if d.is_dir()]
for d in dirs:
    print(f"  ๐Ÿ“ {d.name}")

# List dengan filter ekstensi
python_files = list(project_dir.glob("*.py"))
print(f"\nFile Python: {len(python_files)}")
for f in python_files:
    print(f"  {f.name}")

# List sorted by name
all_files = sorted(project_dir.iterdir(), key=lambda p: p.name)
for f in all_files:
    print(f"  {f.name}")

# List sorted by modification time
from datetime import datetime

recent_files = sorted(
    [f for f in project_dir.iterdir() if f.is_file()],
    key=lambda p: p.stat().st_mtime,
    reverse=True
)
for f in recent_files[:10]:
    mtime = datetime.fromtimestamp(f.stat().st_mtime)
    print(f"  {f.name} โ€” {mtime:%Y-%m-%d %H:%M}")

Recursive Traversal

Python โ€” Recursive Walk
from pathlib import Path

def print_tree(directory, prefix="", max_depth=3, current_depth=0):
    """Mencetak struktur direktori seperti tree command."""
    if current_depth > max_depth:
        return

    items = sorted(directory.iterdir(), key=lambda p: (p.is_file(), p.name))
    for i, item in enumerate(items):
        is_last = (i == len(items) - 1)
        connector = "โ””โ”€โ”€ " if is_last else "โ”œโ”€โ”€ "
        print(f"{prefix}{connector}{item.name}")

        if item.is_dir():
            extension = "    " if is_last else "โ”‚   "
            print_tree(item, prefix + extension, max_depth, current_depth + 1)

# Cetak struktur project
project = Path("my_project")
print(f"๐Ÿ“ {project.name}/")
print_tree(project)

# Menggunakan rglob untuk rekursif
print("\n=== Semua file .py secara rekursif ===")
for py_file in Path(".").rglob("*.py"):
    print(f"  {py_file}")

# Walk manual dengan level kedalaman
def walk_with_depth(path, depth=0, max_depth=2):
    """Generator yang yield (path, depth) tuples."""
    if depth > max_depth:
        return
    for item in sorted(path.iterdir()):
        yield item, depth
        if item.is_dir():
            yield from walk_with_depth(item, depth + 1, max_depth)

for path, depth in walk_with_depth(Path(".")):
    indent = "  " * depth
    icon = "๐Ÿ“" if path.is_dir() else "๐Ÿ“„"
    print(f"{indent}{icon} {path.name}")

6. Globbing dan Pattern Matching

Globbing adalah salah satu fitur paling powerful dari pathlib. Dengan glob, kita bisa mencari file dan direktori berdasarkan pola tertentu.

Dasar Glob

Python โ€” Glob Basics
from pathlib import Path

project = Path(".")

# glob() โ€” mencari di direktori saat ini saja
# * = semua file
all_items = list(project.glob("*"))
print(f"Semua item: {len(all_items)}")

# *.py = semua file Python
py_files = list(project.glob("*.py"))
for f in py_files:
    print(f"  {f}")

# *.txt = semua file teks
txt_files = list(project.glob("*.txt"))

# ? = satu karakter apapun
# Mencari file dengan nama 1 karakter + .py
single_char = list(project.glob("?.py"))
print(f"File 1 karakter .py: {single_char}")

# [abc] = karakter dalam kurung
abc_files = list(project.glob("[abc]*.py"))
print(f"File dimulai a/b/c: {abc_files}")

# [0-9] = range karakter
num_files = list(project.glob("[0-9]*"))
print(f"File dimulai angka: {num_files}")

# ** = rekursif ke semua subdirektori
all_py_recursive = list(project.rglob("*.py"))
print(f"Semua .py (rekursif): {len(all_py_recursive)}")

# rglob() = shorthand untuk **/pattern
# project.rglob("*.py") sama dengan project.glob("**/*.py")

Pattern Lanjutan

Python โ€” Advanced Glob
from pathlib import Path

base = Path("src")

# Mencari semua file Python di subdirektori src
for py in base.rglob("*.py"):
    print(py)

# Mencari file test
test_files = list(base.rglob("test_*.py"))
print(f"Test files: {len(test_files)}")

# Mencari file dengan beberapa ekstensi
import itertools

extensions = ["*.py", "*.js", "*.ts", "*.html", "*.css"]
source_files = []
for ext in extensions:
    source_files.extend(base.rglob(ext))
print(f"Source files: {len(source_files)}")

# Atau gunakan itertools.chain
from itertools import chain
all_sources = list(chain.from_iterable(
    base.rglob(ext) for ext in ["*.py", "*.js", "*.ts"]
))

# Mencari file berdasarkan pattern kompleks
# File yang mengandung "config" di nama
config_files = list(base.rglob("*config*"))
print(f"Config files: {config_files}")

# File dengan ganda ekstensi (misal .tar.gz)
archives = list(base.rglob("*.tar.gz"))

# Mencari hanya direktori (folders)
all_dirs = [p for p in base.rglob("*") if p.is_dir()]
print(f"Directories: {len(all_dirs)}")

# Pattern dengan negasi menggunakan filter
non_test = [f for f in base.rglob("*.py") if not f.name.startswith("test_")]
print(f"Non-test Python files: {len(non_test)}")

Filter Hasil Glob

Python โ€” Filter Glob Results
from pathlib import Path
from datetime import datetime, timedelta

base = Path(".")

# Filter berdasarkan ukuran
large_files = [
    f for f in base.rglob("*")
    if f.is_file() and f.stat().st_size > 1_000_000  # > 1MB
]
for f in large_files:
    size_mb = f.stat().st_size / 1_000_000
    print(f"  {f} โ€” {size_mb:.2f} MB")

# Filter berdasarkan waktu modifikasi
one_week_ago = datetime.now() - timedelta(days=7)
recent_files = [
    f for f in base.rglob("*.py")
    if f.is_file() and datetime.fromtimestamp(f.stat().st_mtime) > one_week_ago
]
print(f"File Python dimodifikasi minggu ini: {len(recent_files)}")

# Filter berdasarkan nama (menggunakan regex)
import re

pattern = re.compile(r"^v\d+\.\d+\.\d+.*\.py$")
versioned = [
    f for f in base.rglob("*.py")
    if pattern.match(f.name)
]

# Menggabungkan beberapa kriteria
def is_interesting(path: Path) -> bool:
    """File yang menarik: Python, > 100 baris, bukan test."""
    if not path.suffix == ".py":
        return False
    if path.name.startswith("test_"):
        return False
    if path.name.startswith("__"):
        return False
    try:
        lines = path.read_text().count('\n')
        return lines > 100
    except (OSError, UnicodeDecodeError):
        return False

interesting = [f for f in base.rglob("*.py") if is_interesting(f)]
print(f"File Python menarik: {len(interesting)}")

7. Operasi File dan Directory

Pathlib menyediakan method-method untuk melakukan operasi file system seperti membuat, menghapus, menyalin, dan memindahkan file.

Membuat Directory

Python โ€” Create Directory
from pathlib import Path

# Membuat satu direktori
new_dir = Path("output")
new_dir.mkdir(exist_ok=True)  # Tidak error jika sudah ada

# Membuat direktori bersarang (parent=True)
deep_dir = Path("output/reports/2026/juni")
deep_dir.mkdir(parents=True, exist_ok=True)
print(f"Created: {deep_dir}")

# Membuat direktori sementara
import tempfile

# Temporary directory dengan pathlib
with tempfile.TemporaryDirectory() as tmp:
    tmp_path = Path(tmp)
    (tmp_path / "test.txt").write_text("Hello!")
    print(f"Temp dir: {tmp_path}")
    print(f"Files: {list(tmp_path.iterdir())}")

# Membuat struktur project lengkap
def create_project_structure(base: Path):
    """Membuat struktur project Python standar."""
    dirs = [
        base / "src" / "mypackage",
        base / "tests",
        base / "docs",
        base / "data" / "raw",
        base / "data" / "processed",
        base / "notebooks",
    ]
    for d in dirs:
        d.mkdir(parents=True, exist_ok=True)
        # Buat __init__.py di package
        if "package" in str(d):
            (d / "__init__.py").touch()

    # Buat file standar
    (base / "README.md").write_text("# My Project\n")
    (base / "requirements.txt").write_text("")
    (base / ".gitignore").write_text("__pycache__/\n*.pyc\n.env\n")
    (base / "pyproject.toml").write_text("[project]\nname = 'myproject'\n")

    print(f"Project structure created at {base}")

create_project_structure(Path("my_project"))

Menghapus File dan Directory

Python โ€” Delete Operations
from pathlib import Path
import shutil

# Menghapus file
temp_file = Path("temp.txt")
temp_file.write_text("temporary")
temp_file.unlink()  # Menghapus file
# unlink(missing_ok=True) tidak error jika file tidak ada (Python 3.8+)

# Menghapus direktori kosong
empty_dir = Path("empty_folder")
empty_dir.mkdir(exist_ok=True)
empty_dir.rmdir()  # Hanya untuk direktori kosong

# Menghapus direktori dan isinya (RECURSIVE)
import shutil
build_dir = Path("build")
if build_dir.exists():
    shutil.rmtree(build_dir)

# Safe delete dengan pathlib
def safe_delete(path: Path):
    """Menghapus file atau direktori dengan aman."""
    if not path.exists():
        print(f"โš ๏ธ {path} tidak ditemukan")
        return

    if path.is_file():
        path.unlink()
        print(f"๐Ÿ—‘๏ธ File dihapus: {path}")
    elif path.is_dir():
        shutil.rmtree(path)
        print(f"๐Ÿ—‘๏ธ Directory dihapus: {path}")

# Clean cache files
def clean_cache(base: Path):
    """Menghapus semua __pycache__ dan .pyc files."""
    count = 0
    for cache_dir in base.rglob("__pycache__"):
        shutil.rmtree(cache_dir)
        count += 1

    for pyc in base.rglob("*.pyc"):
        pyc.unlink()
        count += 1

    print(f"๐Ÿงน Dihapus {count} cache items")

clean_cache(Path("."))

Menyalin dan Memindahkan

Python โ€” Copy & Move
from pathlib import Path
import shutil

src = Path("data/input.csv")
dst = Path("backup/input_backup.csv")

# Pastikan direktori tujuan ada
dst.parent.mkdir(parents=True, exist_ok=True)

# Menyalin file
shutil.copy2(src, dst)  # copy2 mempertahankan metadata

# Memindahkan / rename file
old_path = Path("temp_file.txt")
new_path = Path("renamed_file.txt")
old_path.rename(new_path)

# Memindahkan ke direktori lain
target_dir = Path("archive")
target_dir.mkdir(exist_ok=True)
new_path.rename(target_dir / new_path.name)

# Menyalin seluruh direktori
src_dir = Path("project_src")
dst_dir = Path("project_backup")
if src_dir.exists():
    shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True)

# Rename dengan pathlib (bisa untuk memindahkan juga)
p = Path("old_name.py")
if p.exists():
    p.rename(p.parent / "new_name.py")

# Membuat symlink (symbolic link)
target = Path("original.txt")
link = Path("link_to_original.txt")
if target.exists() and not link.exists():
    link.symlink_to(target)
    print(f"Symlink: {link} โ†’ {target}")

# Mengecek apakah symlink
if link.is_symlink():
    print(f"Target: {link.resolve()}")

8. Permission dan Metadata

Pathlib memungkinkan kita untuk mengakses metadata file dan mengatur permission melalui method stat().

File Statistics

Python โ€” File Stats
from pathlib import Path
from datetime import datetime

p = Path("data/report.csv")

if p.exists():
    stat = p.stat()

    # Ukuran file
    print(f"Size: {stat.st_size} bytes")
    print(f"Size: {stat.st_size / 1024:.2f} KB")

    # Waktu
    created = datetime.fromtimestamp(stat.st_ctime)
    modified = datetime.fromtimestamp(stat.st_mtime)
    accessed = datetime.fromtimestamp(stat.st_atime)

    print(f"Created:  {created:%Y-%m-%d %H:%M:%S}")
    print(f"Modified: {modified:%Y-%m-%d %H:%M:%S}")
    print(f"Accessed: {accessed:%Y-%m-%d %H:%M:%S}")

    # Permission (Unix only)
    import octal
    mode = stat.st_mode
    print(f"Mode: {octal(mode)}")

    # Cek tipe file
    print(f"Is file:      {p.is_file()}")
    print(f"Is directory: {p.is_dir()}")
    print(f"Is symlink:   {p.is_symlink()}")
    print(f"Is absolute:  {p.is_absolute()}")
else:
    print(f"File {p} tidak ditemukan")

# Ringkasan ukuran direktori
def dir_size(path: Path) -> int:
    """Menghitung total ukuran direktori."""
    total = 0
    for item in path.rglob("*"):
        if item.is_file():
            total += item.stat().st_size
    return total

def format_size(size_bytes: int) -> str:
    """Format ukuran ke string yang readable."""
    for unit in ['B', 'KB', 'MB', 'GB']:
        if size_bytes < 1024:
            return f"{size_bytes:.2f} {unit}"
        size_bytes /= 1024
    return f"{size_bytes:.2f} TB"

project_dir = Path(".")
total = dir_size(project_dir)
print(f"\nTotal ukuran project: {format_size(total)}")

File Permission (Unix)

Python โ€” Permissions
import os
from pathlib import Path

script = Path("deploy.sh")

# Mengubah permission (Unix/Linux/Mac)
if script.exists():
    # chmod 755 (rwxr-xr-x)
    script.chmod(0o755)
    print(f"Permission set to 755")

    # Membaca permission
    mode = script.stat().st_mode
    # Cek permission tertentu
    is_executable = bool(mode & 0o111)
    is_readable = bool(mode & 0o444)
    is_writable = bool(mode & 0o222)

    print(f"Executable: {is_executable}")
    print(f"Readable:   {is_readable}")
    print(f"Writable:   {is_writable}")

# Membuat file executable
def make_executable(path: Path):
    """Membuat file bisa dieksekusi."""
    if path.exists():
        current = path.stat().st_mode
        path.chmod(current | 0o111)
        print(f"โœ… {path} is now executable")

# Readonly
def make_readonly(path: Path):
    """Membuat file hanya-baca."""
    if path.exists():
        path.chmod(0o444)
        print(f"๐Ÿ”’ {path} is now read-only")

# Owner only
def make_private(path: Path):
    """Membuat file hanya bisa diakses owner."""
    if path.exists():
        path.chmod(0o600)
        print(f"๐Ÿ” {path} is now private (owner only)")

9. Pathlib vs os.path

Berikut perbandingan lengkap antara pathlib modern dengan pendekatan lama menggunakan os.path.

Operasi os.path (Lama) pathlib (Modern)
Gabung pathos.path.join(a, b)a / b
Parent diros.path.dirname(p)p.parent
Nama fileos.path.basename(p)p.name
Ekstensios.path.splitext(p)[1]p.suffix
Cek adaos.path.exists(p)p.exists()
Cek fileos.path.isfile(p)p.is_file()
Cek diros.path.isdir(p)p.is_dir()
Path absolutos.path.abspath(p)p.resolve()
Home diros.path.expanduser("~")Path.home()
Read fileopen(p).read()p.read_text()
Walkos.walk(d)d.rglob("*")

Kompatibilitas dengan os

Python โ€” Interoperability
from pathlib import Path
import os

# Path object bisa digunakan di mana string diperlukan
p = Path("/home/user/file.txt")

# os.path functions menerima Path object
print(os.path.exists(p))     # True/False
print(os.path.getsize(p))    # Ukuran file

# os functions juga menerima Path object
os.chdir(Path.home())

# Menggunakan Path dengan subprocess
import subprocess
script = Path("/usr/bin/python3")
result = subprocess.run(
    [str(script), "--version"],  # Perlu convert ke str untuk beberapa kasus
    capture_output=True, text=True
)
print(result.stdout)

# Menggunakan fsspec atau pathlib dengan open()
p = Path("data.csv")
with open(p, "r") as f:  # Path otomatis diterima
    content = f.read()

# Convert antara string dan Path
path_str = "/home/user/file.txt"
p = Path(path_str)      # String โ†’ Path
s = str(p)              # Path โ†’ String

# Menggunakan os.fspath (Python 3.6+)
print(os.fspath(p))  # Menghasilkan string

10. Best Practices dan Tips

Tips Penggunaan

Python โ€” Best Practices
from pathlib import Path

# โœ… GUNAKAN: Operator / untuk gabung path
config = Path.home() / ".config" / "myapp" / "settings.json"

# โŒ HINDARI: Menggunakan str + concatenation
# config = Path(str(Path.home()) + "/.config/myapp/settings.json")

# โœ… GUNAKAN: resolve() untuk path absolut tanpa symlink
abs_path = Path("relative/file.txt").resolve()

# โœ… GUNAKAN: exist_ok=True untuk mkdir
Path("output/data").mkdir(parents=True, exist_ok=True)

# โœ… GUNAKAN: missing_ok=True untuk unlink (Python 3.8+)
Path("temp_file.txt").unlink(missing_ok=True)

# โœ… GUNAKAN: rglob() untuk pencarian rekursif
all_tests = list(Path(".").rglob("test_*.py"))

# โœ… GUNAKAN: Type hints dengan Path
def read_config(path: Path) -> dict:
    import json
    return json.loads(path.read_text())

# โœ… GUNAKAN: Path.cwd() dan Path.home()
project_root = Path.cwd()
user_home = Path.home()

# โœ… GUNAKAN: with_suffix untuk mengubah ekstensi
source = Path("data.csv")
backup = source.with_suffix(".csv.bak")

# โœ… GUNAKAN: iterdir() untuk list direktori
for item in Path(".").iterdir():
    print(item)

Common Patterns

Python โ€” Common Patterns
from pathlib import Path
from typing import List, Generator
import hashlib

# Pattern 1: Find all files of certain types
def find_files(directory: Path, extensions: List[str]) -> List[Path]:
    """Mencari file berdasarkan ekstensi."""
    results = []
    for ext in extensions:
        results.extend(directory.rglob(f"*{ext}"))
    return sorted(results, key=lambda p: p.name)

# Pattern 2: File hash calculator
def file_hash(path: Path, algorithm: str = "sha256") -> str:
    """Menghitung hash file."""
    h = hashlib.new(algorithm)
    h.update(path.read_bytes())
    return h.hexdigest()

# Pattern 3: Safe file writing (write to temp, then rename)
def safe_write(path: Path, content: str, encoding: str = "utf-8"):
    """Menulis file dengan aman (atomic write)."""
    import tempfile
    tmp_fd, tmp_path = tempfile.mkstemp(dir=path.parent, suffix=".tmp")
    try:
        tmp = Path(tmp_path)
        tmp.write_text(content, encoding=encoding)
        tmp.rename(path)
    except:
        Path(tmp_path).unlink(missing_ok=True)
        raise

# Pattern 4: Directory walker dengan yield
def walk_files(directory: Path) -> Generator[Path, None, None]:
    """Generator yang mengyield semua file secara rekursif."""
    for item in directory.iterdir():
        if item.is_file():
            yield item
        elif item.is_dir():
            yield from walk_files(item)

# Pattern 5: Backup file sebelum overwrite
def backup_write(path: Path, content: str):
    """Menulis file dengan backup otomatis."""
    if path.exists():
        backup = path.with_suffix(f"{path.suffix}.bak")
        import shutil
        shutil.copy2(path, backup)
    path.write_text(content)

# Pattern 6: Temporary file dengan context manager
import tempfile
from contextlib import contextmanager

@contextmanager
def temp_file(suffix: str = ".tmp"):
    """Context manager untuk temporary file."""
    tmp = Path(tempfile.mktemp(suffix=suffix))
    try:
        yield tmp
    finally:
        tmp.unlink(missing_ok=True)

# Usage
with temp_file(".json") as tmp:
    tmp.write_text('{"test": true}')
    print(tmp.read_text())
# File otomatis dihapus

11. Studi Kasus Nyata

Project Organizer

Python โ€” File Organizer
"""
File Organizer โ€” Mengorganisir file berdasarkan ekstensi
Gunakan: python organizer.py /path/to/downloads
"""
from pathlib import Path
import shutil
import sys
from datetime import datetime

# Mapping ekstensi ke kategori
FILE_TYPES = {
    "Images":      [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp"],
    "Documents":   [".pdf", ".doc", ".docx", ".txt", ".odt", ".rtf"],
    "Spreadsheets":[".xls", ".xlsx", ".csv", ".ods"],
    "Presentations":[".ppt", ".pptx", ".odp"],
    "Videos":      [".mp4", ".avi", ".mkv", ".mov", ".wmv"],
    "Audio":       [".mp3", ".wav", ".flac", ".aac", ".ogg"],
    "Archives":    [".zip", ".rar", ".tar", ".gz", ".7z"],
    "Code":        [".py", ".js", ".ts", ".html", ".css", ".java", ".cpp"],
}

def get_category(suffix: str) -> str:
    """Mendapatkan kategori berdasarkan ekstensi."""
    suffix = suffix.lower()
    for category, extensions in FILE_TYPES.items():
        if suffix in extensions:
            return category
    return "Others"

def organize_downloads(download_dir: Path, dry_run: bool = True):
    """Mengorganisir file di direktori downloads."""
    if not download_dir.is_dir():
        print(f"โŒ {download_dir} bukan direktori!")
        return

    moved = 0
    for item in download_dir.iterdir():
        if item.is_dir() or item.name.startswith("."):
            continue

        category = get_category(item.suffix)
        target_dir = download_dir / category
        target_file = target_dir / item.name

        # Handle duplikat
        counter = 1
        while target_file.exists():
            stem = item.stem
            target_file = target_dir / f"{stem}_{counter}{item.suffix}"
            counter += 1

        if dry_run:
            print(f"  [DRY RUN] {item.name} โ†’ {category}/")
        else:
            target_dir.mkdir(exist_ok=True)
            shutil.move(str(item), str(target_file))
            print(f"  โœ… {item.name} โ†’ {category}/")
            moved += 1

    print(f"\n{'Would move' if dry_run else 'Moved'} {moved} files")

# Jalankan
if __name__ == "__main__":
    target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.home() / "Downloads"
    print(f"๐Ÿ“ Organizing: {target}\n")
    organize_downloads(target, dry_run=True)  # Set False untuk eksekusi

Log Analyzer

Python โ€” Log Analyzer
"""
Log Analyzer โ€” Menganalisis file log untuk mencari error patterns
"""
from pathlib import Path
from collections import Counter
from datetime import datetime
from typing import Dict, List

def analyze_logs(log_dir: Path) -> Dict:
    """Menganalisis semua file .log di direktori."""
    stats = {
        "total_files": 0,
        "total_lines": 0,
        "errors": Counter(),
        "warnings": Counter(),
        "recent_errors": [],
    }

    log_files = sorted(log_dir.rglob("*.log"), key=lambda p: p.name)
    stats["total_files"] = len(log_files)

    for log_file in log_files:
        try:
            lines = log_file.read_text(encoding="utf-8", errors="replace").splitlines()
            stats["total_lines"] += len(lines)

            for i, line in enumerate(lines, 1):
                if "ERROR" in line:
                    # Extract error message
                    msg = line.split("ERROR", 1)[-1].strip()[:100]
                    stats["errors"][msg] += 1
                    stats["recent_errors"].append({
                        "file": str(log_file),
                        "line": i,
                        "message": msg,
                    })
                elif "WARNING" in line:
                    msg = line.split("WARNING", 1)[-1].strip()[:100]
                    stats["warnings"][msg] += 1

        except Exception as e:
            print(f"โš ๏ธ Error reading {log_file}: {e}")

    return stats

def print_report(stats: Dict):
    """Mencetak laporan analisis."""
    print("=" * 60)
    print("๐Ÿ“Š LOG ANALYSIS REPORT")
    print("=" * 60)
    print(f"Total log files: {stats['total_files']}")
    print(f"Total lines:     {stats['total_lines']:,}")
    print(f"Error count:     {sum(stats['errors'].values()):,}")
    print(f"Warning count:   {sum(stats['warnings'].values()):,}")

    if stats["errors"]:
        print("\n๐Ÿ”ด Top 5 Errors:")
        for msg, count in stats["errors"].most_common(5):
            print(f"  [{count:4d}] {msg}")

    if stats["warnings"]:
        print("\n๐ŸŸก Top 5 Warnings:")
        for msg, count in stats["warnings"].most_common(5):
            print(f"  [{count:4d}] {msg}")

# Jalankan
if __name__ == "__main__":
    log_directory = Path("/var/log")  # Sesuaikan
    if log_directory.exists():
        stats = analyze_logs(log_directory)
        print_report(stats)

Template Generator

Python โ€” Template Generator
"""
Project Template Generator โ€” Membuat struktur project dari template
"""
from pathlib import Path

TEMPLATES = {
    "python": {
        "files": {
            "README.md": "# {name}\n\nDeskripsi project.\n",
            "pyproject.toml": """[project]
name = "{name}"
version = "0.1.0"
description = ""
authors = [{{name = "Author", email = "author@email.com"}}]
requires-python = ">=3.10"

[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]
""",
            "src/{name_snake}/__init__.py": '"""Package {name}."""\n__version__ = "0.1.0"\n',
            "src/{name_snake}/main.py": """def main():
    print("Hello from {name}!")

if __name__ == "__main__":
    main()
""",
            "tests/__init__.py": "",
            "tests/test_main.py": """from {name_snake}.main import main

def test_main(capsys):
    main()
    captured = capsys.readouterr()
    assert "Hello" in captured.out
""",
            ".gitignore": "__pycache__/\n*.pyc\n.env\n*.egg-info/\ndist/\nbuild/\n",
        },
        "dirs": ["src/{name_snake}", "tests", "docs"],
    },
    "web": {
        "files": {
            "README.md": "# {name}\n\nWeb project.\n",
            "index.html": "\n\n{name}\n\n",
            "css/style.css": "/* {name} styles */\nbody {{ margin: 0; padding: 0; }}\n",
            "js/app.js": "// {name} main script\nconsole.log('{name} loaded');\n",
            ".gitignore": "node_modules/\n.env\ndist/\n",
        },
        "dirs": ["css", "js", "images"],
    },
}

def create_project(name: str, template: str, base_dir: Path):
    """Membuat project dari template."""
    if template not in TEMPLATES:
        print(f"โŒ Template '{template}' tidak ditemukan!")
        print(f"   Tersedia: {', '.join(TEMPLATES.keys())}")
        return

    t = TEMPLATES[template]
    project_dir = base_dir / name

    if project_dir.exists():
        print(f"โŒ {project_dir} sudah ada!")
        return

    # Format variables
    vars = {
        "name": name,
        "name_snake": name.lower().replace("-", "_").replace(" ", "_"),
    }

    # Create directories
    for d in t["dirs"]:
        dir_path = project_dir / d.format(**vars)
        dir_path.mkdir(parents=True, exist_ok=True)

    # Create files
    for filepath, content in t["files"].items():
        file_path = project_dir / filepath.format(**vars)
        file_path.parent.mkdir(parents=True, exist_ok=True)
        file_path.write_text(content.format(**vars), encoding="utf-8")

    print(f"โœ… Project '{name}' created at {project_dir}")
    print(f"   Template: {template}")
    print(f"   Files: {len(t['files'])}")

# Usage
create_project("my-api", "python", Path("projects"))
create_project("my-website", "web", Path("projects"))

12. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Python Pathlib:

Pertanyaan 1: Bagaimana cara menggabungkan path menggunakan pathlib?

a) Path.join(base, "sub", "file.txt")
b) base / "sub" / "file.txt"
c) base + "sub" + "file.txt"
d) Path.combine(base, "sub", "file.txt")

Pertanyaan 2: Apa perbedaan antara glob() dan rglob()?

a) Tidak ada perbedaan
b) glob() mencari rekursif, rglob() hanya di direktori saat ini
c) glob() hanya di direktori saat ini, rglob() mencari rekursif ke semua subdirektori
d) rglob() hanya untuk file, glob() untuk direktori

Pertanyaan 3: Apa yang dikembalikan oleh Path("/home/user/file.txt").stem?

a) "file.txt"
b) "file"
c) ".txt"
d) "txt"

Pertanyaan 4: Method apa yang digunakan untuk membaca isi file teks secara langsung?

a) path.read()
b) path.read_file()
c) path.read_text()
d) path.get_content()

Pertanyaan 5: Bagaimana cara membuat direktori beserta parent-nya sekaligus?

a) path.mkdir(parents=True)
b) path.makedirs()
c) path.mkdir(recursive=True)
d) path.create_dirs()