M13 — Làm việc với File

← 12 - Module, Package và Thư viện chuẩn | Tiếp theo → 14 - Lập trình hướng đối tượng (OOP)

Mục tiêu module

  • Đọc và ghi file văn bản bằng open() và with
  • Hiểu encoding và xử lý tiếng Việt
  • Đọc/ghi file CSV và JSON
  • Xử lý lỗi khi thao tác file

Thời lượng: ~8 giờ | Tuần: 20–21

Tuần 20: Mục 1–6 (file text) | Tuần 21: Mục 7–11 (CSV, JSON) + ôn tập giai đoạn 3


PHẦN 1 — TUẦN 20

1. Vì sao cần làm việc với file?

Đến giờ, mọi dữ liệu bạn xử lý đều biến mất khi chương trình kết thúc. File cho phép:

  • Lưu dữ liệu lâu dài (persistence)
  • Đọc dữ liệu từ nguồn bên ngoài (Excel xuất CSV, log hệ thống, file cấu hình)
  • Xuất báo cáo để gửi cho người khác

2. open() và with

2.1. Cách cũ (KHÔNG khuyến nghị)

f = open("du_lieu.txt", "r", encoding="utf-8")
noi_dung = f.read()
f.close()                     # BẮT BUỘC phải đóng
print(noi_dung)

Vấn đề: nếu có lỗi xảy ra giữa open và close, file không được đóng → khóa file, rò rỉ tài nguyên.

2.2. Cách chuẩn — with (KHUYẾN NGHỊ)

with open("du_lieu.txt", "r", encoding="utf-8") as f:
    noi_dung = f.read()
    print(noi_dung)
# File TỰ ĐỘNG đóng ở đây, kể cả khi có lỗi

with là context manager (trình quản lý ngữ cảnh) — tự động dọn dẹp tài nguyên khi ra khỏi khối lệnh.

Quy tắc

Luôn dùng with khi mở file. Không có ngoại lệ nào đáng kể cho người mới.


3. Chế độ mở file (mode)

ModeTênTác dụngFile chưa tồn tại
"r"readĐọc (mặc định)❌ FileNotFoundError
"w"writeGhi — XÓA SẠCH nội dung cũ✅ Tạo mới
"a"appendGhi thêm vào cuối file✅ Tạo mới
"x"exclusiveTạo mới — lỗi nếu file đã có✅ Tạo mới
"r+"read/writeVừa đọc vừa ghi❌ Lỗi

Thêm "b" để làm việc với file nhị phân: "rb", "wb" (ảnh, PDF, Excel…).

Mode "w" XÓA SẠCH file cũ

with open("quan_trong.txt", "w", encoding="utf-8") as f:
    f.write("abc")

Nếu quan_trong.txt có 10.000 dòng dữ liệu, sau lệnh này chỉ còn abc. Không có Undo.

Muốn thêm dữ liệu vào file có sẵn → dùng "a".


4. ⚠️ Encoding — Bảng mã (QUAN TRỌNG VỚI TIẾNG VIỆT)

4.1. Luôn chỉ định encoding

# ❌ SAI — dùng encoding mặc định của hệ điều hành
with open("du_lieu.txt", "r") as f:
    ...
 
# ✅ ĐÚNG
with open("du_lieu.txt", "r", encoding="utf-8") as f:
    ...

Trên Windows tiếng Việt, encoding mặc định thường là cp1258 hoặc cp1252, không phải UTF-8. Đọc file UTF-8 mà không chỉ định sẽ gây:

UnicodeDecodeError: 'charmap' codec can't decode byte 0x81 in position 15

Hoặc tệ hơn: đọc được nhưng chữ bị lỗi thành Nguy?n V?n A hoặc Nguyên Văn A.

4.2. Các encoding thường gặp

EncodingDùng khi
utf-8✅ Mặc định nên dùng cho mọi trường hợp
utf-8-sigFile CSV do Excel tạo (có BOM ở đầu file)
cp1258File cũ tạo trên Windows tiếng Việt
latin-1Đọc “cứu hộ” — không bao giờ lỗi nhưng có thể sai chữ

BOM và Excel

Excel khi lưu file CSV thường thêm 3 byte đặc biệt ở đầu (BOM — Byte Order Mark). Nếu đọc bằng utf-8, ký tự đầu tiên sẽ thành \ufeff:

# ❌ Cột đầu tiên bị lỗi tên
with open("tu_excel.csv", encoding="utf-8") as f: ...
 
# ✅ Tự động bỏ BOM
with open("tu_excel.csv", encoding="utf-8-sig") as f: ...

Ngược lại, khi ghi file CSV để mở bằng Excel, dùng encoding="utf-8-sig" để Excel hiển thị đúng tiếng Việt.

4.3. Xử lý lỗi encoding

# Bỏ qua ký tự lỗi
with open("file.txt", encoding="utf-8", errors="ignore") as f:
    ...
 
# Thay ký tự lỗi bằng dấu ?
with open("file.txt", encoding="utf-8", errors="replace") as f:
    ...

Chỉ dùng khi bắt buộc — vì bạn đang mất dữ liệu.


5. Đọc file văn bản

Giả sử có file khach_hang.txt:

KH001|Nguyễn Văn A|Huế|5000000
KH002|Trần Thị B|TP HCM|12000000
KH003|Lê Văn C|Hà Nội|0

5.1. read() — Đọc toàn bộ thành một chuỗi

with open("khach_hang.txt", encoding="utf-8") as f:
    noi_dung = f.read()
print(noi_dung)
print(type(noi_dung))        # <class 'str'>

⚠️ Không dùng với file lớn (hàng GB) vì nạp hết vào RAM.

5.2. readlines() — Đọc thành list các dòng

with open("khach_hang.txt", encoding="utf-8") as f:
    cac_dong = f.readlines()
 
print(cac_dong)
# ['KH001|Nguyễn Văn A|Huế|5000000\n', 'KH002|...\n', ...]

Chú ý: mỗi dòng còn ký tự xuống dòng \n ở cuối.

5.3. Duyệt từng dòng (KHUYẾN NGHỊ)

with open("khach_hang.txt", encoding="utf-8") as f:
    for dong in f:
        print(dong.strip())      # strip() bỏ ký tự \n

Cách này tiết kiệm bộ nhớ nhất — đọc từng dòng một, không nạp hết vào RAM. Dùng được với file cực lớn.

5.4. Ví dụ đầy đủ — Đọc và phân tích

from pathlib import Path
 
DUONG_DAN = Path("khach_hang.txt")
 
def doc_khach_hang(duong_dan):
    """
    Đọc file khách hàng, trả về list các dict.
 
    Định dạng file: MaKH|TenKH|Tinh|CongNo (phân cách bằng |)
 
    Trả về:
        tuple: (danh sách khách hàng, danh sách lỗi)
    """
    khach_hang = []
    loi = []
 
    try:
        with open(duong_dan, encoding="utf-8") as f:
            for so_dong, dong in enumerate(f, start=1):
                dong = dong.strip()
 
                if not dong:                  # Bỏ qua dòng trống
                    continue
                if dong.startswith("#"):      # Bỏ qua dòng comment
                    continue
 
                phan = dong.split("|")
                if len(phan) != 4:
                    loi.append(f"Dong {so_dong}: sai so cot ({len(phan)}/4)")
                    continue
 
                try:
                    cong_no = float(phan[3])
                except ValueError:
                    loi.append(f"Dong {so_dong}: cong no khong phai so: '{phan[3]}'")
                    continue
 
                khach_hang.append({
                    "ma": phan[0].strip(),
                    "ten": phan[1].strip(),
                    "tinh": phan[2].strip(),
                    "cong_no": cong_no,
                })
 
    except FileNotFoundError:
        print(f"Loi: Khong tim thay file '{duong_dan}'")
    except PermissionError:
        print(f"Loi: Khong co quyen doc file '{duong_dan}'")
    except UnicodeDecodeError as e:
        print(f"Loi bang ma khi doc file: {e}")
 
    return khach_hang, loi
 
 
ds, cac_loi = doc_khach_hang(DUONG_DAN)
 
print(f"Doc thanh cong {len(ds)} ban ghi, {len(cac_loi)} dong loi")
print()
 
if cac_loi:
    print("CHI TIET LOI:")
    for l in cac_loi:
        print(f"  - {l}")
    print()
 
print(f"{'MA':<8}{'TEN KHACH HANG':<22}{'TINH':<12}{'CONG NO':>15}")
print("-" * 57)
for kh in ds:
    print(f"{kh['ma']:<8}{kh['ten']:<22}{kh['tinh']:<12}{kh['cong_no']:>15,.0f}")
print("-" * 57)
print(f"{'TONG CONG':<42}{sum(k['cong_no'] for k in ds):>15,.0f}")

6. Ghi file văn bản

6.1. write() — Ghi một chuỗi

with open("bao_cao.txt", "w", encoding="utf-8") as f:
    f.write("BAO CAO CONG NO\n")
    f.write("=" * 40 + "\n")
    f.write("Ngay lap: 08/08/2026\n")

⚠️ write() không tự xuống dòng — phải tự thêm \n.

6.2. writelines() — Ghi nhiều dòng

cac_dong = [
    "Dong 1\n",
    "Dong 2\n",
    "Dong 3\n",
]
 
with open("output.txt", "w", encoding="utf-8") as f:
    f.writelines(cac_dong)

writelines() cũng không tự thêm \n.

6.3. Ghi thêm với mode "a"

from datetime import datetime
 
def ghi_log(thong_diep, duong_dan="he_thong.log"):
    """Ghi một dòng log kèm thời gian."""
    thoi_gian = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(duong_dan, "a", encoding="utf-8") as f:
        f.write(f"[{thoi_gian}] {thong_diep}\n")
 
 
ghi_log("Bat dau chuong trinh")
ghi_log("Doc file khach hang thanh cong: 150 ban ghi")
ghi_log("LOI: Khong ket noi duoc database")

6.4. Xuất báo cáo ra file

def xuat_bao_cao(khach_hang, duong_dan="bao_cao_cong_no.txt"):
    """Xuất báo cáo công nợ ra file văn bản."""
    W = 60
    tong = sum(k["cong_no"] for k in khach_hang)
 
    with open(duong_dan, "w", encoding="utf-8") as f:
        f.write("=" * W + "\n")
        f.write("BAO CAO CONG NO KHACH HANG".center(W) + "\n")
        f.write(f"Ngay lap: {datetime.now():%d/%m/%Y %H:%M}".center(W) + "\n")
        f.write("=" * W + "\n")
        f.write(f"{'MA':<8}{'TEN KHACH HANG':<25}{'CONG NO':>27}\n")
        f.write("-" * W + "\n")
 
        for kh in sorted(khach_hang, key=lambda k: k["cong_no"], reverse=True):
            f.write(f"{kh['ma']:<8}{kh['ten']:<25}{kh['cong_no']:>27,.0f}\n")
 
        f.write("-" * W + "\n")
        f.write(f"{'TONG CONG':<33}{tong:>27,.0f}\n")
        f.write("=" * W + "\n")
 
    print(f"Da xuat bao cao ra file: {duong_dan}")

PHẦN 2 — TUẦN 21

7. File CSV

CSV = Comma-Separated Values (giá trị phân cách bằng dấu phẩy) — định dạng phổ biến nhất để trao đổi dữ liệu bảng, đọc được bằng Excel.

7.1. Vì sao không tự split(",")?

# ❌ Cách thủ công — SAI với dữ liệu thực tế
dong = 'KH001,"Cong ty ABC, Chi nhanh Hue",5000000'
print(dong.split(","))
# ['KH001', '"Cong ty ABC', ' Chi nhanh Hue"', '5000000']   <- SAI! bị tách 4 phần

Dữ liệu thật thường có dấu phẩy bên trong dấu nháy kép. Module csv xử lý đúng các trường hợp này.

7.2. Đọc CSV — csv.reader

File hang_hoa.csv:

ma,ten,don_vi,so_luong,don_gia
VT001,Bàn làm việc,Cái,3,2500000
VT002,"Ghế xoay, có tựa đầu",Cái,10,850000
VT003,Tủ hồ sơ,Cái,2,4200000
import csv
 
with open("hang_hoa.csv", newline="", encoding="utf-8-sig") as f:
    doc = csv.reader(f)
    tieu_de = next(doc)              # Đọc dòng đầu (header)
    print("Cot:", tieu_de)
 
    for dong in doc:
        print(dong)                  # Mỗi dòng là một list
Cot: ['ma', 'ten', 'don_vi', 'so_luong', 'don_gia']
['VT001', 'Bàn làm việc', 'Cái', '3', '2500000']
['VT002', 'Ghế xoay, có tựa đầu', 'Cái', '10', '850000']
['VT003', 'Tủ hồ sơ', 'Cái', '2', '4200000']

Bắt buộc có newline=""

Đây là yêu cầu chính thức trong tài liệu Python. Nếu thiếu, trên Windows file CSV ghi ra sẽ thừa một dòng trống giữa mỗi dòng dữ liệu.

7.3. Đọc CSV — csv.DictReader (KHUYẾN NGHỊ)

import csv
 
with open("hang_hoa.csv", newline="", encoding="utf-8-sig") as f:
    doc = csv.DictReader(f)
    for dong in doc:
        print(dong["ten"], "-", dong["don_gia"])

DictReader tự dùng dòng đầu làm tên khóa → truy cập bằng tên cột thay vì chỉ số. Dễ đọc và ít lỗi hơn nhiều.

⚠️ Mọi giá trị đọc lên đều là chuỗi — phải ép kiểu:

so_luong = int(dong["so_luong"])
don_gia = float(dong["don_gia"])

7.4. Ghi CSV — csv.writer

import csv
 
du_lieu = [
    ["ma", "ten", "cong_no"],
    ["KH001", "Nguyễn Văn A", 5_000_000],
    ["KH002", "Công ty ABC, Chi nhánh Huế", 12_000_000],
]
 
with open("xuat.csv", "w", newline="", encoding="utf-8-sig") as f:
    ghi = csv.writer(f)
    ghi.writerows(du_lieu)

Module csv tự thêm dấu nháy kép quanh giá trị có chứa dấu phẩy.

7.5. Ghi CSV — csv.DictWriter

import csv
 
khach_hang = [
    {"ma": "KH001", "ten": "Nguyễn Văn A", "tinh": "Huế", "cong_no": 5_000_000},
    {"ma": "KH002", "ten": "Trần Thị B", "tinh": "TP HCM", "cong_no": 12_000_000},
]
 
with open("khach_hang.csv", "w", newline="", encoding="utf-8-sig") as f:
    cot = ["ma", "ten", "tinh", "cong_no"]
    ghi = csv.DictWriter(f, fieldnames=cot)
    ghi.writeheader()                    # Ghi dòng tiêu đề
    ghi.writerows(khach_hang)

7.6. ⚠️ Dấu phân cách của Excel Việt Nam

Excel cài đặt tiếng Việt/khu vực Việt Nam thường dùng dấu chấm phẩy ; làm phân cách CSV, vì dấu phẩy đã dùng làm dấu thập phân.

# Đọc file CSV do Excel VN xuất
with open("tu_excel.csv", newline="", encoding="utf-8-sig") as f:
    doc = csv.DictReader(f, delimiter=";")
    ...
 
# Ghi file CSV để Excel VN mở đúng cột
with open("cho_excel.csv", "w", newline="", encoding="utf-8-sig") as f:
    ghi = csv.DictWriter(f, fieldnames=cot, delimiter=";")
    ...

Checklist khi làm việc với CSV + Excel + tiếng Việt

  1. newline="" — luôn có
  2. encoding="utf-8-sig" — để Excel hiển thị đúng tiếng Việt
  3. delimiter=";" — nếu Excel dùng khu vực Việt Nam
  4. Ép kiểu sau khi đọc — mọi giá trị đều là chuỗi

7.7. Chương trình hoàn chỉnh — Xử lý CSV

"""Đọc file CSV hàng hóa, tính toán và xuất báo cáo."""
 
import csv
from decimal import Decimal
from pathlib import Path
 
FILE_VAO = Path("hang_hoa.csv")
FILE_RA = Path("bao_cao_ton_kho.csv")
 
 
def doc_hang_hoa(duong_dan):
    """Đọc file CSV hàng hóa, trả về (danh sách, lỗi)."""
    hang = []
    loi = []
 
    if not duong_dan.exists():
        loi.append(f"Khong tim thay file: {duong_dan}")
        return hang, loi
 
    try:
        with open(duong_dan, newline="", encoding="utf-8-sig") as f:
            doc = csv.DictReader(f)
 
            cot_bat_buoc = {"ma", "ten", "so_luong", "don_gia"}
            thieu = cot_bat_buoc - set(doc.fieldnames or [])
            if thieu:
                loi.append(f"File thieu cot: {', '.join(sorted(thieu))}")
                return hang, loi
 
            for so_dong, dong in enumerate(doc, start=2):   # start=2 vì dòng 1 là header
                try:
                    hang.append({
                        "ma": dong["ma"].strip(),
                        "ten": dong["ten"].strip(),
                        "don_vi": dong.get("don_vi", "").strip(),
                        "so_luong": int(dong["so_luong"]),
                        "don_gia": Decimal(dong["don_gia"]),
                    })
                except (ValueError, KeyError) as e:
                    loi.append(f"Dong {so_dong}: {e}")
 
    except UnicodeDecodeError as e:
        loi.append(f"Loi bang ma: {e}")
    except PermissionError:
        loi.append(f"Khong co quyen doc file: {duong_dan}")
 
    return hang, loi
 
 
def xuat_bao_cao_csv(hang, duong_dan):
    """Xuất báo cáo tồn kho ra file CSV cho Excel."""
    cot = ["stt", "ma", "ten", "don_vi", "so_luong", "don_gia", "thanh_tien"]
 
    with open(duong_dan, "w", newline="", encoding="utf-8-sig") as f:
        ghi = csv.DictWriter(f, fieldnames=cot, delimiter=";")
        ghi.writeheader()
 
        for i, h in enumerate(hang, start=1):
            ghi.writerow({
                "stt": i,
                "ma": h["ma"],
                "ten": h["ten"],
                "don_vi": h["don_vi"],
                "so_luong": h["so_luong"],
                "don_gia": h["don_gia"],
                "thanh_tien": h["so_luong"] * h["don_gia"],
            })
 
    print(f"Da xuat bao cao: {duong_dan.resolve()}")
 
 
def main():
    hang, loi = doc_hang_hoa(FILE_VAO)
 
    if loi:
        print("CO LOI KHI DOC FILE:")
        for l in loi:
            print(f"  - {l}")
        print()
 
    if not hang:
        print("Khong co du lieu de xu ly.")
        return
 
    # In ra màn hình
    print(f"{'MA':<8}{'TEN HANG':<26}{'SL':>6}{'DON GIA':>15}{'THANH TIEN':>17}")
    print("-" * 72)
    tong = Decimal("0")
    for h in hang:
        tt = h["so_luong"] * h["don_gia"]
        tong += tt
        print(f"{h['ma']:<8}{h['ten']:<26}{h['so_luong']:>6}"
              f"{h['don_gia']:>15,.0f}{tt:>17,.0f}")
    print("-" * 72)
    print(f"{'TONG GIA TRI TON KHO':<55}{tong:>17,.0f}")
 
    xuat_bao_cao_csv(hang, FILE_RA)
 
 
if __name__ == "__main__":
    main()

8. File JSON

8.1. Ghi JSON — json.dump()

import json
 
du_lieu = {
    "cong_ty": "Công ty TNHH ABC",
    "mst": "3300123456",
    "khach_hang": [
        {"ma": "KH001", "ten": "Nguyễn Văn A", "cong_no": 5_000_000},
        {"ma": "KH002", "ten": "Trần Thị B", "cong_no": 12_000_000},
    ],
    "ngay_cap_nhat": "2026-08-08",
}
 
with open("du_lieu.json", "w", encoding="utf-8") as f:
    json.dump(du_lieu, f, ensure_ascii=False, indent=2)

8.2. Đọc JSON — json.load()

import json
 
with open("du_lieu.json", encoding="utf-8") as f:
    du_lieu = json.load(f)
 
print(du_lieu["cong_ty"])
for kh in du_lieu["khach_hang"]:
    print(f"  {kh['ma']}: {kh['ten']} - {kh['cong_no']:,}")

8.3. Phân biệt 4 hàm

HàmChiềuĐối tượng
json.dump(obj, file)Python → fileFile
json.dumps(obj)Python → chuỗiChuỗi
json.load(file)File → PythonFile
json.loads(s)Chuỗi → PythonChuỗi

Mẹo nhớ: chữ s cuối = string (chuỗi).

8.4. ⚠️ JSON không lưu được mọi kiểu Python

import json
from datetime import date
from decimal import Decimal
 
du_lieu = {"ngay": date.today()}
json.dumps(du_lieu)      # ❌ TypeError: Object of type date is not JSON serializable
 
du_lieu = {"tien": Decimal("100.50")}
json.dumps(du_lieu)      # ❌ TypeError: Object of type Decimal is not JSON serializable

Cách xử lý — chuyển đổi trước:

du_lieu = {
    "ngay": date.today().isoformat(),         # "2026-08-08"
    "tien": str(Decimal("100.50")),           # "100.50"
}
print(json.dumps(du_lieu, ensure_ascii=False))

Hoặc dùng tham số default:

def chuyen_doi(o):
    if isinstance(o, (date, datetime)):
        return o.isoformat()
    if isinstance(o, Decimal):
        return str(o)
    raise TypeError(f"Khong the chuyen doi kieu {type(o)}")
 
json.dumps(du_lieu, default=chuyen_doi, ensure_ascii=False)

8.5. Ứng dụng — File cấu hình

import json
from pathlib import Path
 
FILE_CAU_HINH = Path("cau_hinh.json")
 
CAU_HINH_MAC_DINH = {
    "ten_cong_ty": "Công ty TNHH ABC",
    "mst": "3300123456",
    "thue_suat_vat": 0.1,
    "nguong_chiet_khau": 10000000,
    "duong_dan_xuat": "./bao_cao",
}
 
 
def doc_cau_hinh():
    """Đọc cấu hình, tạo file mặc định nếu chưa có."""
    if not FILE_CAU_HINH.exists():
        ghi_cau_hinh(CAU_HINH_MAC_DINH)
        print(f"Da tao file cau hinh mac dinh: {FILE_CAU_HINH}")
        return CAU_HINH_MAC_DINH.copy()
 
    try:
        with open(FILE_CAU_HINH, encoding="utf-8") as f:
            ch = json.load(f)
    except json.JSONDecodeError as e:
        print(f"File cau hinh loi dinh dang: {e}")
        print("Dung cau hinh mac dinh.")
        return CAU_HINH_MAC_DINH.copy()
 
    # Bổ sung khóa thiếu
    for k, v in CAU_HINH_MAC_DINH.items():
        ch.setdefault(k, v)
    return ch
 
 
def ghi_cau_hinh(ch):
    """Ghi cấu hình ra file."""
    with open(FILE_CAU_HINH, "w", encoding="utf-8") as f:
        json.dump(ch, f, ensure_ascii=False, indent=2)
 
 
if __name__ == "__main__":
    ch = doc_cau_hinh()
    print(f"Cong ty     : {ch['ten_cong_ty']}")
    print(f"Thue suat   : {ch['thue_suat_vat']:.0%}")

9. Kiểm tra và quản lý file với pathlib

from pathlib import Path
 
p = Path("du_lieu/khach_hang.csv")
 
# Kiểm tra
print(p.exists())                    # File có tồn tại không
print(p.is_file())                   # Có phải file không
print(p.is_dir())                    # Có phải thư mục không
 
# Thông tin
print(p.name)                        # khach_hang.csv
print(p.stem)                        # khach_hang
print(p.suffix)                      # .csv
print(p.parent)                      # du_lieu
print(p.resolve())                   # Đường dẫn tuyệt đối
if p.exists():
    print(p.stat().st_size, "bytes") # Kích thước file
 
# Tạo thư mục
Path("bao_cao/2026/thang_08").mkdir(parents=True, exist_ok=True)
 
# Liệt kê file
thu_muc = Path("du_lieu")
for f in thu_muc.glob("*.csv"):              # Chỉ thư mục này
    print(f)
for f in thu_muc.glob("**/*.csv"):           # Cả thư mục con
    print(f)
 
# Đọc/ghi nhanh (file nhỏ)
noi_dung = Path("ghi_chu.txt").read_text(encoding="utf-8")
Path("ghi_chu.txt").write_text("Noi dung moi", encoding="utf-8")
 
# Xóa, đổi tên
# p.unlink()                          # Xóa file
# p.rename("ten_moi.csv")             # Đổi tên

10. Thực hành bắt buộc

Bài 1 — Ghi và đọc file text

  1. Tạo file nhat_ky.txt, ghi 5 dòng bất kỳ
  2. Đọc lại và in ra kèm số thứ tự dòng
  3. Thêm 2 dòng nữa (mode "a"), đọc lại kiểm tra
  4. Đếm số dòng, số từ, số ký tự của file

Bài 2 — Phân tích file log

Tạo file he_thong.log với nội dung mẫu:

2026-08-08 08:15:00 INFO Khoi dong he thong
2026-08-08 08:15:03 INFO Ket noi database thanh cong
2026-08-08 09:22:11 WARNING Bo nho su dung 85%
2026-08-08 10:05:47 ERROR Khong the ghi file bao cao
2026-08-08 10:05:48 ERROR Timeout khi goi API
2026-08-08 11:30:00 INFO Hoan tat dong bo du lieu

Viết chương trình:

  1. Đếm số dòng theo từng mức (INFO/WARNING/ERROR)
  2. In ra chỉ các dòng ERROR
  3. Tìm dòng ERROR đầu tiên và cuối cùng
  4. Xuất các dòng ERROR ra file loi.txt

Bài 3 — Đọc CSV

Tạo file nhan_vien.csv với các cột: ma, ho_ten, phong_ban, luong, ngay_vao_lam. Nhập ít nhất 10 dòng dữ liệu (có tiếng Việt có dấu).

Viết chương trình:

  1. Đọc file bằng DictReader
  2. In bảng danh sách nhân viên
  3. Tính tổng và trung bình lương
  4. Nhóm theo phòng ban (số người, tổng lương)
  5. Tìm 3 người lương cao nhất
  6. Tính thâm niên (dùng datetime ở 12 - Module, Package và Thư viện chuẩn)

Bài 4 — Ghi CSV cho Excel

Từ dữ liệu bài 3, xuất ra file bao_cao_nhan_su.csv sao cho mở bằng Excel tiếng Việt hiển thị đúng (đúng cột, đúng dấu tiếng Việt). Kiểm chứng bằng cách mở thật bằng Excel.

Bài 5 — JSON

  1. Chuyển dữ liệu nhân viên ở bài 3 sang file JSON
  2. Đọc lại file JSON, in ra
  3. Thêm một nhân viên mới rồi ghi lại
  4. Thử ghi một date vào JSON, xử lý lỗi phát sinh

Bài 6 — Chuyển đổi định dạng

Viết chương trình chuyển đổi qua lại: CSV → JSON và JSON → CSV.

Bài 7 — Xử lý lỗi file

Viết hàm doc_file_an_toan(duong_dan) xử lý đủ:

  • File không tồn tại
  • Không có quyền đọc
  • Lỗi encoding
  • Đường dẫn là thư mục chứ không phải file
  • File rỗng

Mỗi trường hợp có thông báo riêng, hàm không bao giờ ném lỗi ra ngoài.

Bài 8 — ÔN TẬP GIAI ĐOẠN 3

Viết chương trình so_thu_chi.py:

  • Lưu trữ: file JSON giao_dich.json
  • Chức năng:
    1. Thêm giao dịch (ngày, loại thu/chi, danh mục, số tiền, ghi chú)
    2. Xem danh sách (có lọc theo tháng, theo loại)
    3. Sửa/xóa giao dịch
    4. Báo cáo tổng thu, tổng chi, số dư
    5. Báo cáo theo danh mục
    6. Xuất báo cáo ra CSV cho Excel
  • Yêu cầu kỹ thuật:
    • Chia thành ít nhất 3 module
    • Mọi hàm có docstring
    • Xử lý lỗi đầy đủ (không bao giờ sập)
    • Dùng Decimal cho tiền
    • Dùng datetime cho ngày
    • Dữ liệu tự động lưu sau mỗi thay đổi

Đây là bài kiểm tra giai đoạn 3.


11. Tự kiểm tra

  • Tôi luôn dùng with open(...) thay vì open() + close()
  • Tôi luôn chỉ định encoding="utf-8"
  • Tôi biết utf-8-sig dùng cho file CSV liên quan Excel
  • Tôi biết mode "w" xóa sạch file cũ
  • Tôi biết duyệt file theo dòng để tiết kiệm bộ nhớ
  • Tôi biết csv.DictReader và csv.DictWriter
  • Tôi biết newline="" là bắt buộc với module csv
  • Tôi phân biệt json.dump và json.dumps
  • Tôi biết ensure_ascii=False cho tiếng Việt
  • Tôi xử lý được FileNotFoundError, PermissionError, UnicodeDecodeError

12. Tài liệu chính thức

NguồnNội dungLink
Tutorial — Reading and Writing FilesNhập môn filehttps://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files
open()Tài liệu hàm open, mode, encodinghttps://docs.python.org/3/library/functions.html#open
csv moduleĐọc/ghi CSVhttps://docs.python.org/3/library/csv.html
json moduleĐọc/ghi JSONhttps://docs.python.org/3/library/json.html
pathlibĐường dẫn hướng đối tượnghttps://docs.python.org/3/library/pathlib.html
Standard EncodingsDanh sách encodinghttps://docs.python.org/3/library/codecs.html#standard-encodings
Unicode HOWTOXử lý Unicode và encodinghttps://docs.python.org/3/howto/unicode.html

Tiếp theo → 14 - Lập trình hướng đối tượng (OOP)