Câu 1Cái nào là cách được khuyến nghị để xây đường dẫn không hỏng trên cả Windows hay Linux?
os và pathlib — Đường dẫn file và thao tác thư mục
Học module os.path và pathlib của Python từ căn bản. Xây đường dẫn không phụ thuộc OS, liệt kê và duyệt đệ quy thư mục, và tách đường dẫn thành các phần với đối tượng Path — đều có thực hành.
Hai module xử lý đường dẫn file và thư mục — os.path cũ và pathlib mới hơn, dễ đọc hơn. Bài này đi qua xây đường dẫn không phụ thuộc OS, liệt kê và duyệt đệ quy thư mục, và tách đường dẫn thành các phần với đối tượng Path, theo thứ tự đó.
os.path — Xây đường dẫn không phụ thuộc OS
Dấu phân tách đường dẫn khác nhau theo OS. Windows dùng \ (backslash), còn Linux và macOS dùng /. Hard-code `"data/sales/2024.csv"` trực tiếp trong code chạy trên Linux và Mac, nhưng Windows có thể đọc sai đường dẫn lúc chạy.
Nếu bạn tách các phần và truyền chúng cho os.path.join("data", "sales", "2024.csv"), Python chọn dấu phân tách đúng tại chỗ dựa trên OS đang chạy.
/ trên Linux / Mac và \ trên Windows. Đừng nướng dấu phân tách bằng tay — đó là mẹo để portable.| Hàm | Ý nghĩa | Ví dụ |
|---|---|---|
| os.path.join(*parts) | Nối đường dẫn với dấu phân tách OS | join('data', 'sales') → 'data/sales' |
| os.path.exists(p) | Đường dẫn có tồn tại không | True / False |
| os.path.isfile(p) | Có phải là file (không phải thư mục) | True / False |
| os.path.isdir(p) | Có phải là thư mục | True / False |
| os.path.basename(p) | Tên file hoặc folder cuối | basename('data/x.csv') → 'x.csv' |
| os.path.dirname(p) | Đường dẫn cha với phần cuối bỏ đi | dirname('data/x.csv') → 'data' |
| os.path.splitext(p) | Tách phần mở rộng | splitext('x.csv') → ('x', '.csv') |
Thực hành 2 — Tách tên và phần mở rộng với basename và splitext
Đừng nhồi tất cả vào một dòng — gán từng bước vào biến trung gian và tách tên file khỏi phần mở rộng. basename lấy tên file cuối từ đường dẫn đầy đủ, và splitext tách tên đó thành tuple (name, extension).
os.listdir và os.walk — Liệt kê thư mục và duyệt đệ quy
Khi bạn muốn kéo nội dung folder vào Python, dùng os.listdir cho chỉ một cấp và os.walk để đệ quy đi xuống các subfolder. os.listdir trả về danh sách tên (cả file và subfolder) trực tiếp bên trong folder bạn chỉ định, còn os.walk đi cả subtree đệ quy và yield tuple (đường dẫn hiện tại, danh sách tên subfolder, danh sách tên file) từng cấp một.
import os
# Một cấp: tên trực tiếp dưới 'data'
print(os.listdir("data"))
# → ['sales', 'inventory']
# Đệ quy: đi mọi thứ dưới 'data'
for dirpath, dirnames, filenames in os.walk("data"):
print(dirpath, filenames)
# → data ['sales', 'inventory'] []
# data/sales [] ['2024_q1.csv', '2024_q2.csv']
# data/inventory [] ['items.json']
glob — Pattern matching để gom file
Khi bạn muốn lấy chỉ file khớp điều kiện — như chỉ file có phần mở rộng `.csv` — module glob là đường ngắn nhất. Viết mục tiêu dưới dạng pattern dùng wildcard như * (chuỗi bất kỳ) hoặc ** (độ sâu bất kỳ) và bạn nhận lại danh sách đường dẫn khớp.
* khớp chuỗi bất kỳ trong cùng cấp, ** qua bất kỳ số cấp nào (cần recursive=True).import glob
# File CSV trực tiếp dưới data/sales
print(glob.glob("data/sales/*.csv"))
# → ['data/sales/2024_q1.csv', 'data/sales/2024_q2.csv']
# Tìm đệ quy dưới data (** + recursive=True)
print(glob.glob("data/**/*.csv", recursive=True))
# → ['data/sales/2024_q1.csv', 'data/sales/2024_q2.csv']
Wildcard ** của glob đi đôi với recursive=True
Dấu sao đôi trong glob.glob("data/**/*.csv") là wildcard qua bất kỳ số cấp nào. Nhưng không có recursive=True, nó hoạt động như * thường và sẽ không tìm gì ở folder sâu hơn. Luôn truyền tham số đó khi bạn muốn tìm đệ quy.
pathlib.Path — Thao tác đường dẫn hướng đối tượng
Trong khi os.path là thư viện xử lý đường dẫn dưới dạng chuỗi, từ Python 3.4 cách tiếp cận được khuyến nghị là pathlib.Path, xử lý chính đường dẫn dưới dạng đối tượng. Xây một cái với Path("data/sales/2024_q1.csv") và bạn truy cập từng phần qua thuộc tính như .parent cho folder cha, .name cho phần cuối, .stem cho tên không có phần mở rộng, và .suffix cho phần mở rộng.
os.path.dirname / basename / splitext riêng.from pathlib import Path
p = Path("data") / "sales" / "2024_q1.csv" # Nối với toán tử /
print(p) # data/sales/2024_q1.csv
print(p.parent) # data/sales
print(p.name) # 2024_q1.csv
print(p.stem) # 2024_q1
print(p.suffix) # .csv
print(p.exists()) # True
# Đọc nội dung (wrapper quanh with open)
print(p.read_text()) # Nội dung CSV
# Liệt kê subfolder (tương đương os.walk)
for sub in Path("data").rglob("*.csv"):
print(sub)
`os.path` dựa trên chuỗi, `pathlib.Path` dựa trên đối tượng — chúng cung cấp cùng các thao tác. Bảng dưới ánh xạ mỗi tác vụ giữa chúng.
| Bạn muốn gì | Phong cách os.path | Phong cách pathlib |
|---|---|---|
| Nối | os.path.join('data', 'x.csv') | Path('data') / 'x.csv' |
| Folder cha | os.path.dirname(p) | p.parent |
| Tên file | os.path.basename(p) | p.name |
| Tên không có extension | Dùng os.path.splitext(p)[0] | p.stem |
| Extension | os.path.splitext(p)[1] | p.suffix |
| Kiểm tra tồn tại | os.path.exists(p) | p.exists() |
| Tìm đệ quy | glob.glob('**/*.csv', recursive=True) | Path('.').rglob('*.csv') |
| Đọc | with open(p) as f: f.read() | p.read_text() |
Chọn pathlib cho code mới
Pathlib được khuyến nghị cho code mới. Khi API thư viện cũ yêu cầu đường dẫn chuỗi (một số driver DB chẳng hạn), chuyển với str(p). os.path không đi đâu cả, nên biết cả hai ánh xạ giữ bạn thoải mái khi đọc code legacy nữa.
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Cái nào sau đây phù hợp nhất để đi mọi cấp của folder đệ quy?
Câu 3Cho p = Path("data/sales/2024_q1.csv"), giá trị của p.stem là gì?