⭐ Vision Engineer · Chặng 1Bài 6/11Bài học~60 phút

Python cơ bản: đọc ghi file, pathlib và xử lý lỗi

Đọc ghi file UTF-8 với with, đường dẫn bằng pathlib, try/except đúng cách và hàm đọc cấu hình JSON an toàn.

Python cơ bản: đọc ghi file, pathlib và xử lý lỗi

Học xong bài này, bạn sẽ

  • Đọc ghi file văn bản với with
  • Làm việc với đường dẫn bằng pathlib
  • Bắt lỗi bằng try/except đúng cách

Nên học trước: Hàm và module

Nội dung bài viết
  1. 1. Đọc và ghi file văn bản với with
  2. 2. pathlib: làm việc với đường dẫn
  3. 3. Ngoại lệ: khi có lỗi lúc chạy
  4. 4. Ứng dụng: file cấu hình JSON
  5. Tóm tắt
  6. Bài tập

Chương trình thật phải đọc cấu hình từ file, ghi kết quả ra file, và không được "chết" chỉ vì một ảnh hỏng hay một file thiếu. Bài này dạy ba kỹ năng đó: đọc ghi file văn bản, làm việc với đường dẫn bằng pathlib, và xử lý lỗi bằng try/except. Cuối bài bạn sẽ có hàm đọc file cấu hình JSON an toàn, thứ mọi trạm vision đều cần.

1. Đọc và ghi file văn bản với with

with open("ket_qua_ca.txt", "w", encoding="utf-8") as f:
    f.write("SP-01,OK\n")
    f.write("SP-02,NG,xước\n")

with open("ket_qua_ca.txt", "a", encoding="utf-8") as f:   # "a": ghi thêm vào cuối
    f.write("SP-03,OK\n")

with open("ket_qua_ca.txt", encoding="utf-8") as f:
    for dong in f:
        print(dong.strip().split(","))
['SP-01', 'OK']
['SP-02', 'NG', 'xước']
['SP-03', 'OK']
  • Chế độ mở: "r" đọc (mặc định), "w" ghi mới (xóa nội dung cũ), "a" ghi thêm.
  • with tự đóng file khi xong, kể cả khi có lỗi. Luôn dùng with thay vì open() rồi quên close().
  • Luôn ghi encoding="utf-8". Trên Windows, mặc định có thể không phải UTF-8 và chữ tiếng Việt sẽ bị lỗi.
  • "\n" là ký tự xuống dòng; .strip() bỏ nó khi đọc.

2. pathlib: làm việc với đường dẫn

Đừng ghép đường dẫn bằng cộng chuỗi ("anh" + "\\" + ten): sai ngay khi chuyển từ Windows sang Linux. Dùng pathlib.Path và toán tử /:

from pathlib import Path

goc = Path("du_lieu")
thu_muc_ng = goc / "2026-10-09" / "NG"
thu_muc_ng.mkdir(parents=True, exist_ok=True)   # tạo cả cây thư mục, không lỗi nếu đã có

file_anh = thu_muc_ng / "ST3_000128_NG.png"
print(file_anh)                  # du_lieu/2026-10-09/NG/ST3_000128_NG.png (Windows hiện dấu \)
print(file_anh.name)             # ST3_000128_NG.png
print(file_anh.stem)             # ST3_000128_NG
print(file_anh.suffix)           # .png
print(file_anh.parent.name)      # NG
print(file_anh.exists())         # False (chưa tạo file)
print(file_anh.with_suffix(".jpg").name)   # ST3_000128_NG.jpg

Liệt kê file

for i in range(3):
    (thu_muc_ng / f"anh_{i}.png").write_text("demo")   # tạo vài file thử

ds_anh = sorted(thu_muc_ng.glob("*.png"))              # glob: tìm theo mẫu tên
print(len(ds_anh), [p.name for p in ds_anh])

tat_ca = list(goc.rglob("*.png"))                      # rglob: tìm cả thư mục con
print(len(tat_ca))
3 ['anh_0.png', 'anh_1.png', 'anh_2.png']
3

Path.read_text() và Path.write_text() là cách ngắn gọn để đọc ghi cả file văn bản một lần (cũng nên truyền encoding="utf-8" khi có tiếng Việt).

3. Ngoại lệ: khi có lỗi lúc chạy

Khi gặp lỗi, Python "ném" ra một ngoại lệ (exception). Không ai bắt thì chương trình dừng. Với trạm kiểm tra chạy 24/7, một file ảnh hỏng không được phép làm dừng cả dây chuyền. try/except bắt ngoại lệ và xử lý:

gia_tri_doc = ["12.48", "12,51", "", "12.50"]   # dữ liệu "bẩn" từ file

ket_qua = []
for s in gia_tri_doc:
    try:
        ket_qua.append(float(s))
    except ValueError:
        print(f"Bỏ qua giá trị không hợp lệ: {s!r}")

print(ket_qua)
Bỏ qua giá trị không hợp lệ: '12,51'
Bỏ qua giá trị không hợp lệ: ''
[12.48, 12.5]

{s!r} in chuỗi kèm dấu nháy để thấy rõ cả chuỗi rỗng. Thực tế bạn có thể sửa "12,51" thành "12.51" bằng .replace(",", ".") trước khi ép kiểu.

Đầy đủ: try / except / else / finally

from pathlib import Path


def doc_so_luong(duong_dan):
    try:
        with open(duong_dan, encoding="utf-8") as f:
            n = int(f.read().strip())
    except FileNotFoundError:
        print("Không thấy file:", duong_dan)
        return 0
    except ValueError:
        print("Nội dung file không phải số nguyên")
        return 0
    else:
        print("Đọc thành công")          # chỉ chạy khi không có lỗi
        return n
    finally:
        print("Kết thúc đọc file")       # luôn chạy, có lỗi hay không


Path("dem.txt").write_text("1284", encoding="utf-8")
print(doc_so_luong("dem.txt"))
print(doc_so_luong("khong_co.txt"))
Đọc thành công
Kết thúc đọc file
1284
Không thấy file: khong_co.txt
Kết thúc đọc file
0

Các ngoại lệ hay gặp

Ngoại lệKhi nào
FileNotFoundErrorMở file không tồn tại
ValueErrorGiá trị sai dạng: int("abc")
KeyErrorKhóa không có trong dict
IndexErrorChỉ số vượt quá độ dài list
ZeroDivisionErrorChia cho 0
PermissionErrorKhông có quyền ghi (file đang mở trong Excel…)
Đừng "nuốt" mọi lỗiViết except: trống hoặc except Exception: pass sẽ che mất lỗi thật, khiến bạn không biết vì sao chương trình chạy sai. Chỉ bắt đúng loại lỗi bạn biết cách xử lý, và luôn ghi lại (in hoặc log) lỗi đó.

Tự ném lỗi với raise

def kiem_tra_nguong(nguong: int) -> int:
    if not 0 <= nguong <= 255:
        raise ValueError(f"Ngưỡng phải trong khoảng 0–255, nhận {nguong}")
    return nguong


try:
    kiem_tra_nguong(300)
except ValueError as loi:
    print("Cấu hình sai:", loi)
Cấu hình sai: Ngưỡng phải trong khoảng 0–255, nhận 300

Báo lỗi sớm với thông điệp rõ ràng tốt hơn nhiều so với để chương trình chạy tiếp với giá trị sai rồi cho ra kết quả kiểm tra sai.

4. Ứng dụng: file cấu hình JSON

Tham số thuật toán (ngưỡng, vùng ROI, dung sai) nên nằm trong file cấu hình để người vận hành chỉnh mà không phải sửa code. JSON là định dạng phổ biến nhất, và module json chuyển qua lại giữa file JSON và dict:

import json
from pathlib import Path

MAC_DINH = {"nguong": 128, "dien_tich_min": 500, "roi": [100, 80, 400, 300], "luu_anh_ok": False}


def doc_cau_hinh(duong_dan: str | Path) -> dict:
    """Đọc file cấu hình JSON; thiếu file hoặc thiếu khóa thì dùng giá trị mặc định."""
    duong_dan = Path(duong_dan)
    cau_hinh = dict(MAC_DINH)
    if not duong_dan.exists():
        print(f"Chưa có {duong_dan}, tạo file mặc định")
        duong_dan.write_text(json.dumps(MAC_DINH, indent=2, ensure_ascii=False), encoding="utf-8")
        return cau_hinh
    try:
        cau_hinh.update(json.loads(duong_dan.read_text(encoding="utf-8")))
    except json.JSONDecodeError as loi:
        raise ValueError(f"File {duong_dan} sai cú pháp JSON: {loi}") from loi
    if not 0 <= cau_hinh["nguong"] <= 255:
        raise ValueError("nguong phải trong khoảng 0–255")
    return cau_hinh


cfg = doc_cau_hinh("cau_hinh.json")      # lần đầu: tạo file mặc định
cfg = doc_cau_hinh("cau_hinh.json")      # lần sau: đọc từ file
print(cfg["nguong"], cfg["roi"])
print(Path("cau_hinh.json").read_text(encoding="utf-8"))
Chưa có cau_hinh.json, tạo file mặc định
128 [100, 80, 400, 300]
{
  "nguong": 128,
  "dien_tich_min": 500,
  "roi": [
    100,
    80,
    400,
    300
  ],
  "luu_anh_ok": false
}

Ba chi tiết đáng học:

  • ensure_ascii=False giữ chữ tiếng Việt dễ đọc trong file JSON.
  • Bắt đầu từ giá trị mặc định rồi update bằng nội dung file: file thiếu khóa nào thì vẫn có giá trị dùng được.
  • Kiểm tra giá trị ngay sau khi đọc và báo lỗi rõ ràng (raise ... from loi giữ lại lỗi gốc để dễ tra).

JSON chỉ có list, không có tuple: (100, 80, 400, 300) lưu xuống rồi đọc lên sẽ thành list [100, 80, 400, 300].

Tóm tắt

  • with open(..., encoding="utf-8"); "w" ghi đè, "a" ghi thêm.
  • Path + / để ghép đường dẫn; mkdir(parents=True, exist_ok=True); glob, rglob.
  • try/except bắt đúng loại lỗi; finally luôn chạy; raise báo lỗi sớm.
  • Cấu hình để trong JSON, có mặc định và kiểm tra giá trị.

Bài tập

Bài 1. Đọc file kết quả an toàn

File do_luong.txt mỗi dòng một giá trị đo, có dòng trống, dòng dùng dấu phẩy thập phân và dòng chữ. Viết hàm trả về list số thực hợp lệ (chấp nhận cả dấu phẩy) và số dòng bị bỏ qua.

Xem đáp án
from pathlib import Path

Path("do_luong.txt").write_text("12.48\n12,51\n\nabc\n12.50\n", encoding="utf-8")


def doc_gia_tri(duong_dan):
    hop_le, bo_qua = [], 0
    for dong in Path(duong_dan).read_text(encoding="utf-8").splitlines():
        dong = dong.strip().replace(",", ".")
        if not dong:
            continue
        try:
            hop_le.append(float(dong))
        except ValueError:
            bo_qua += 1
    return hop_le, bo_qua


print(doc_gia_tri("do_luong.txt"))
([12.48, 12.51, 12.5], 1)

Bài 2. Sắp xếp ảnh theo kết quả

Thư mục anh_moi chứa ảnh tên dạng ST1_000001_OK.png, ST1_000002_NG.png. Viết chương trình chuyển ảnh vào da_phan_loai/OK hoặc da_phan_loai/NG dựa vào phần cuối tên file. Gợi ý: Path.rename() hoặc shutil.move().

Xem đáp án
from pathlib import Path
import shutil

nguon = Path("anh_moi")
nguon.mkdir(exist_ok=True)
for i, kq in enumerate(["OK", "NG", "OK"], start=1):
    (nguon / f"ST1_{i:06d}_{kq}.png").write_text("demo")

dich = Path("da_phan_loai")
for anh in nguon.glob("*.png"):
    ket_qua = anh.stem.split("_")[-1]          # phần cuối tên: OK hoặc NG
    thu_muc = dich / ket_qua
    thu_muc.mkdir(parents=True, exist_ok=True)
    shutil.move(str(anh), str(thu_muc / anh.name))

for p in sorted(dich.rglob("*.png")):
    print(p.parent.name, p.name)
NG ST1_000002_NG.png
OK ST1_000001_OK.png
OK ST1_000003_OK.png

Bài 3. Kiểm tra cấu hình đầy đủ hơn

Mở rộng hàm doc_cau_hinh: báo lỗi nếu roi không có đúng 4 số, hoặc chiều rộng/cao của ROI (phần tử thứ 3, 4) không dương. Thử với file JSON sai để thấy thông báo lỗi.

Gợi ý

Thêm sau dòng kiểm tra ngưỡng: if len(cau_hinh["roi"]) != 4 or cau_hinh["roi"][2] <= 0 or cau_hinh["roi"][3] <= 0: raise ValueError(...). Tạo file sai bằng Path("sai.json").write_text('{"roi": [1, 2, 3]}').

Bài tiếp theo: bạn sẽ tạo môi trường ảo và cài thư viện bằng pip, chuẩn bị cho NumPy và OpenCV.

Bài tiếp theo
Ban biên tập HọcFree

Chúng tôi viết những bài hướng dẫn dễ hiểu, thực tế để ai cũng có thể tự học lập trình và ngoại ngữ, hoàn toàn miễn phí.