Bài 9 · Nâng cao · 22 phút· Cập nhật 11/06/2026
Hệ thống tệp trong Python
Biên soạn bởi Nguyễn Anh Tuấn
Làm việc với tệp trong Python: pathlib & os, context manager (with), đọc/ghi văn bản & nhị phân, JSON/CSV - và cách ghi file an toàn.
pathlib.Path ghép đường dẫn đúng trên mọi OS (toán tử /) và mang sẵn nhiều tiện ích - tìm kiếm, thông tin file:
paths.py
from pathlib import Path
thu_muc = Path("data"); thu_muc.mkdir(exist_ok=True)
tep = thu_muc / "ghi_chu.txt"
tep.write_text("Xin chao", encoding="utf-8")
print(tep.exists(), tep.suffix, tep.stem, tep.stat().st_size)
print([p.name for p in thu_muc.glob("*.txt")]) # tim 1 cap
# thu_muc.rglob("*.py") -> tim DE QUY moi cap con Kết quả khi chạy
True .txt ghi_chu 8 ['ghi_chu.txt']
- ▸Path("data") / "a.txt" ghép đúng trên Windows & Unix.
- ▸glob("*.txt") tìm một cấp; rglob("*.txt") tìm đệ quy; cả hai trả iterator lười.
- ▸.suffix/.stem/.parent/.stat() cho đuôi/tên/thư mục cha/kích thước - khỏi tự cắt str.
with open(...) là một context manager: mở file ở __enter__ và luôn đóng ở __exit__ - kể cả khi có lỗi. Bấm thử kịch bản "Có lỗi":
- __enter__ → mở file
rw.py
with open("nhat_ky.txt", "w", encoding="utf-8") as f:
f.write("dong 1\n"); f.write("dong 2\n")
with open("nhat_ky.txt", encoding="utf-8") as f:
for dong in f: # doc tung dong (luoi, it bo nho)
print(dong.rstrip()) Kết quả khi chạy
dong 1 dong 2
- ▸with open(...) as f: rời khối là file TỰ ĐÓNG - kể cả khi giữa chừng có lỗi.
- ▸for dong in f đọc TỪNG DÒNG (lười) - hợp file lớn.
- ▸Luôn nêu encoding="utf-8" để tiếng Việt không lỗi font.
with không chỉ cho file - bạn tự viết được context manager để “mở rồi chắc chắn dọn” (kết nối, khoá, thư mục tạm…). Hai cách:
ctx.py
# Cach 1: class voi __enter__/__exit__
class Mo:
def __enter__(self):
print("mo tai nguyen"); return "r"
def __exit__(self, exc_type, exc, tb):
print("don dep") # chay du co loi hay khong
# Cach 2 (gon hon): @contextmanager tren mot generator
from contextlib import contextmanager
@contextmanager
def mo():
print("setup") # truoc yield = __enter__
try:
yield 42 # gia tri gan cho "as"
finally:
print("teardown") # sau yield = __exit__ (luon chay)
with mo() as x:
print("dung", x) Kết quả khi chạy
setup dung 42 teardown
- ▸Class: __enter__ (mở, trả thứ cho “as”) + __exit__ (dọn, chạy kể cả khi lỗi).
- ▸@contextmanager: trước yield = setup, sau yield (đặt trong finally) = teardown - gọn hơn.
- ▸Dùng cho mọi cặp “mở/đóng” có kỷ luật: kết nối DB, khoá, đổi cwd, đo thời gian…
json_io.py
import json
nguoi = {"ten": "An", "diem": [8, 9, 10], "dat": True}
with open("nguoi.json", "w", encoding="utf-8") as f:
json.dump(nguoi, f, ensure_ascii=False, indent=2) # giu tieng Viet
with open("nguoi.json", encoding="utf-8") as f:
lai = json.load(f)
print(lai["ten"], lai == nguoi) Kết quả khi chạy
An True
- ▸json.dump/load với tệp; json.dumps/loads với str (gửi qua mạng).
- ▸ensure_ascii=False giữ tiếng Việt; indent=2 cho dễ đọc.
- ▸JSON hợp dữ liệu lồng nhau (dict/list).
csv_io.py
import csv
rows = [{"ten": "An", "diem": 8}, {"ten": "Binh", "diem": 9}]
with open("diem.csv", "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=["ten", "diem"])
w.writeheader(); w.writerows(rows)
with open("diem.csv", newline="", encoding="utf-8") as f:
doc = list(csv.DictReader(f))
print(doc[0]["ten"], len(doc)) Kết quả khi chạy
An 2
- ▸DictWriter/DictReader làm việc theo TÊN CỘT - rõ ràng, ít lỗi thứ tự.
- ▸Nhớ newline="" khi mở file CSV (tránh dòng trống thừa trên Windows).
- ▸CSV hợp xuất Excel/bảng; JSON hợp dữ liệu lồng nhau.
Chế độ nhị phân ("rb"/"wb") cho dữ liệu thô (ảnh, zip). Và một mẫu “pro” quan trọng: ghi an toàn - đừng đè trực tiếp lên file quan trọng:
atomic.py
import os
from pathlib import Path
def ghi_an_toan(path: str, text: str):
p = Path(path)
tmp = p.with_suffix(p.suffix + ".tmp")
tmp.write_text(text, encoding="utf-8") # ghi ra file TAM truoc
os.replace(tmp, p) # doi ten NGUYEN TU -> an toan
ghi_an_toan("config.json", '{"ok": true}') - ▸Chế độ văn bản ("r"/"w") cho str (qua encoding); nhị phân ("rb"/"wb") cho bytes thô (ảnh/zip).
- ▸Mở ảnh bằng chế độ văn bản sẽ HỎNG dữ liệu - chọn đúng chế độ.
- ▸Ghi an toàn: ghi file tạm rồi os.replace → người đọc luôn thấy bản hoàn chỉnh, không bao giờ file dở.
Tiếp theo
Câu hỏi thường gặp
Nối str dễ sai (thiếu/thừa /, khác nhau Windows \ vs Unix /). pathlib dùng toán tử / ghép đúng trên mọi OS, và có sẵn .exists(), .glob(), .suffix, .stem, .parent, .stat()… Code gọn & ít lỗi.
p.glob("*.txt") tìm trong MỘT cấp thư mục; p.rglob("*.txt") (hoặc glob("**/*.txt")) tìm ĐỆ QUY mọi cấp con. Cả hai trả về một iterator các Path (lười) - duyệt bằng for hoặc bọc list().
open() chiếm một file handle của OS; không đóng có thể rò rỉ tài nguyên hoặc mất dữ liệu chưa kịp ghi. with ĐẢM BẢO đóng khi rời khối - kể cả khi giữa chừng có lỗi (gọi __exit__).
Hai cách: (1) một CLASS có __enter__ (mở, trả thứ gán cho "as") và __exit__ (đóng, chạy kể cả khi lỗi); (2) gọn hơn - @contextlib.contextmanager trên một generator: phần TRƯỚC yield là setup, phần SAU là teardown. Dùng khi bạn cần “mở rồi chắc chắn dọn” (kết nối, khoá, đổi thư mục tạm…).
Encoding quy định cách CHỮ → BYTE. Dùng utf-8 để tiếng Việt không lỗi font: open(path, encoding="utf-8"). Với JSON thêm ensure_ascii=False để giữ chữ Việt đọc được thay vì \uXXXX.
Nếu ghi trực tiếp đè lên file mà chương trình chết giữa chừng (mất điện, crash), file gốc hỏng/nửa chừng. Mẫu an toàn: ghi vào file TẠM, ghi xong mới os.replace(tmp, target) - đổi tên là thao tác NGUYÊN TỬ của OS, nên người đọc luôn thấy bản CŨ hoàn chỉnh hoặc bản MỚI hoàn chỉnh, không bao giờ thấy file dở.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Vì sao nên dùng pathlib thay vì nối chuỗi đường dẫn bằng tay?
- 1
pathlib khám phá
Dùng
Path: liệt kê mọi*.pytrong một thư mục (đệ quy), in tên + kích thước (.stat().st_size).Hoàn thành khi: Dùng
rglob("*.py")+.name+.stat(); ra danh sách file kèm kích thước. - 2
with & vì sao an toàn
Dùng widget Bước 2 (kịch bản "Có lỗi"): vì sao file vẫn được đóng dù trong khối
withxảy ra lỗi?Hoàn thành khi:
withluôn gọi__exit__(đóng file) khi rời khối, kể cả khi có lỗi - rồi lỗi mới lan ra. - 3
Tự viết context manager
Viết
@contextmanagerdoi_thu_muc(p): đổi cwd sangp,yield, rồi đổi lại cwd cũ ở teardown.Hoàn thành khi: Trong
with doi_thu_muc(...), cwd làp; ra khỏiwith, cwd trở lại như cũ (kể cả khi lỗi). - 4
JSON round-trip
Ghi một dict (tiếng Việt) ra JSON (
ensure_ascii=False) rồi đọc lại; kiểm khớp.Hoàn thành khi: JSON giữ tiếng Việt đọc được; đọc lại bằng dict gốc.
- 5
Ghi an toàn
Viết hàm
ghi_an_toan(path, text): ghi rapath.tmprồios.replacesangpath.Hoàn thành khi: File đích luôn hoàn chỉnh; nếu lỗi trước
os.replace, file cũ không bị hỏng.