--------------- <> -----------------
--- KHOA HỌC - CÔNG NGHỆ - GIÁO DỤC - VIỆC LÀM ---
--- Học để đi cùng bà con trên thế giới ---

Tìm kiếm trong Blog

Python (27) - Dự án kết thúc giai đoạn 1 (1)

Bài trước: Python (26) - Hàm
-----

27. Dự án kết thúc giai đoạn 1

Nếu bạn tới được bài học này, là bạn đã rất kiên trì. Chúc mừng bạn, mọi thứ khởi đầu đều khó, đều dễ nản, nhưng bạn đã không bỏ cuộc, đi tiếp nhé bạn.

Trước khi kết thúc giai đoạn 1, chúng ta sẽ cùng làm một số dự án. Dự án như một sự tổng hợp các kiến thức đã học, đồng thời bạn cũng được làm quen với các khái niệm mới trong quá trình làm một dự án lập trình.

27.1 Dự án lập trình là gì?

Dự án lập trình là một bài tập thực hành tổng hợp, yêu cầu người học kết hợp nhiều kiến thức và kỹ năng đã tích lũy từ các bài học trước để giải quyết một bài toán thực tế hoàn chỉnh.

Khác với các bài tập nhỏ lẻ chỉ tập trung vào một cú pháp hay một cấu trúc dữ liệu đơn lẻ (như viết một hàm tính tổng, tạo một danh sách hay ghi một dòng vào tập tin), một dự án đòi hỏi bạn phải liên kết tất cả các thành phần đó lại với nhau: từ khâu tiếp nhận thông tin, xử lý dữ liệu bằng các thuật toán, tổ chức các hàm, đến việc bắt lỗi và lưu trữ dữ liệu lâu dài vào đĩa cứng.

27.2 Mục đích của làm dự án

Làm dự án giúp bạn thay đổi nhận thức, học được các kiến thức và kỹ năng sau:

- Hệ thống hóa kiến thức: củng cố và liên kết toàn bộ các khái niệm đã học, ví dụ: các kiến thức căn bản, kiến thức về list, tuple, dictionary, set, xử lý tập tin, try…except, hàm

- Rèn luyện tư duy phân tích bài toán: học cách chia nhỏ một ứng dụng lớn thành các mô-đun nhỏ hơn, các hàm chức năng độc lập

- Hình thành phong cách lập trình chuẩn: đặt tên biến/hàm có ý nghĩa, viết ghi chú, tái sử dụng mã nguồn và bẫy lỗi cẩn thận

- Trải nghiệm quy trình làm phần mềm thực tế: hiểu được luồng phát triển, từ bước lên ý tưởng, thiết kế dữ liệu, viết mã cho đến khi kiểm thử và đóng gói sản phẩm

27.2 Các bước để làm dự án

Dựa trên các kiến thức bạn đã có tới thời điểm này, quy trình phát triển một dự án lập trình cơ bản gồm 5 bước sau:

Bước 1: Phân tích yêu cầu và thiết kế cấu trúc dữ liệu

- Xác định rõ bài toán cần giải quyết, các tính năng cốt lõi (nhập, xuất, tìm kiếm, tính toán, lưu trữ)

- Lựa chọn cấu trúc dữ liệu phù hợp để biểu diễn thông tin trong chương trình (ví dụ: dùng List cho danh sách, Dictionary cho bản ghi chi tiết, Set cho danh mục không trùng lặp, Tuple cho các dữ liệu cố định)

Bước 2: Xây dựng giao diện người dùng

- Thiết kế luồng tương tác với người dùng thông qua giao diện dòng lệnh

- Sử dụng vòng lặp while True kết hợp với cấu trúc rẽ nhánh if...elif...else để điều hướng các chức năng trong trình đơn (menu) chính

Bước 3: Phát triển các hàm chức năng

- Chia nhỏ bài toán và viết các hàm xử lý từng nghiệp vụ cụ thể

- Bọc các đoạn mã nhập liệu hoặc xử lý nguy cơ bằng khối try...except để bẫy lỗi (như nhập sai kiểu dữ liệu, chia cho 0), đảm bảo chương trình không bị dừng đột ngột

Bước 4: Quản lý và Lưu trữ dữ liệu

- Tích hợp các thao tác đọc và ghi tập tin (.csv hoặc .txt) để bảo toàn dữ liệu

- Tự động tải dữ liệu cũ khi khởi động chương trình và cập nhật dữ liệu mới vào đĩa cứng trước khi thoát

Bước 5: Kiểm thử (testing) và Tối ưu mã nguồn (refactoring)

- Chạy thử nghiệm các kịch bản thực tế và các trường hợp biên (nhập chữ vào ô số, dữ liệu rỗng, thiếu tập tin lưu trữ)

- Rà soát lại toàn bộ mã nguồn: bổ sung ghi chú, đặt lại tên biến/hàm rõ ràng và gom nhóm các đoạn mã trùng lặp thành một hàm

27.3 Dự án mẫu

Chúng ta sẽ cùng làm một dự án mẫu.

Bạn có thể vừa đọc phần Tài liệu dự án vừa tự mình lập trình hoặc có thể đọc lướt qua Tài liệu dự án, sau đó chuyển qua phần Hướng dẫn làm từng bước để làm theo, rồi sẽ quay lại đọc Tài liệu dự án kỹ hơn khi lập trình.

27.3.1 Tài liệu dự án

Tên dự án: Quản lý bảng điểm học sinh lớp 7

[Bước 1] Phân tích yêu cầu và thiết kế cấu trúc dữ liệu

Các yêu cầu chức năng

- Tạo danh mục các môn học: Toán, Ngữ văn, Tiếng Anh, KHTN, Lịch sử_Địa lý

- Thêm học sinh vào danh sách lớp

- Nhập điểm Đánh giá thường xuyên (hệ số 1), Giữa kỳ (hệ số 2) và Cuối kỳ (hệ số 3) cho từng môn

- Tính điểm trung bình môn, điểm trung bình chung và xếp loại học lực (Tốt, Khá, Đạt, Chưa đạt)

- Xem toàn bộ bảng điểm lớp học

- Lưu và đọc dữ liệu vào/từ tập tin CSV

Thiết kế cấu trúc dữ liệu

- Tuple (dữ liệu cố định): Lưu hệ số điểm HE_SO = (1, 2, 3)

- Set (danh mục không trùng lặp): MON_HOC = {"Toán", "Ngữ văn", "Tiếng Anh", "KHTN", "Lịch sử_Địa lý"}

- Dictionary (bản ghi chi tiết 1 học sinh):

hoc_sinh = {

    "ma_hs": "HS01",

    "ho_ten": "Nguyễn Văn An",

    "diem": {

        "Toán": {"tx": [8.0, 9.0], "gk": 8.5, "ck": 9.0}

    }

}

- List (quản lý danh sách lớp): danh_sach_lop = [hoc_sinh_1, hoc_sinh_2, ...]

[Bước 2] Xây dựng giao diện người dùng

Chúng ta sẽ sử dụng giao diện dòng lệnh.

Sử dụng vòng lặp while True kết hợp if...elif...else để điều hướng trình đơn:

def main():

    danh_sach_lop = doc_file_csv() # Tải dữ liệu khi khởi động

    while True:

        print("\n=== QUẢN LÝ BẢNG ĐIỂM HỌC SINH LỚP 7 ===")

        print("1. Xem danh sách & bảng điểm")

        print("2. Thêm học sinh mới")

        print("3. Nhập điểm môn học")

        print("4. Lưu dữ liệu ra file CSV")

        print("0. Thoát chương trình")

        

        lua_chon = input("Mời chọn chức năng (0-4): ").strip()

        if lua_chon == "1":

            hien_thi_bang_diem(danh_sach_lop)

        elif lua_chon == "2":

            them_hoc_sinh(danh_sach_lop)

        elif lua_chon == "3":

            nhap_diem_hoc_sinh(danh_sach_lop)

        elif lua_chon == "4":

            luu_file_csv(danh_sach_lop)

        elif lua_chon == "0":

            luu_file_csv(danh_sach_lop)

            print("Đã thoát chương trình!")

            break

        else:

            print("Lựa chọn không hợp lệ!")


[Bước 3] Phát triển các hàm chức năng

Viết các hàm nghiệp vụ, áp dụng try...except để bẫy lỗi nhập liệu:

def nhap_diem_hop_le(prompt):

    """Bẫy lỗi nhập điểm: bắt buộc nhập số từ 0.0 đến 10.0"""

    while True:

        try:

            diem = float(input(prompt))

            if 0.0 <= diem <= 10.0:

                return diem

            print("Lỗi: Điểm phải từ 0 đến 10!")

        except ValueError:

            print("Lỗi: Giá trị nhập phải là số!")


def tinh_dtb_mon(diem_mon):

    """Tính ĐTB môn theo hệ số (TX*1 + GK*2 + CK*3) / Tổng hệ số"""

    tx = diem_mon.get("tx", [])

    gk = diem_mon.get("gk", 0.0)

    ck = diem_mon.get("ck", 0.0)

    tong_diem = sum(tx) + (gk * 2) + (ck * 3)

    tong_he_so = len(tx) + 2 + 3

    return round(tong_diem / tong_he_so, 2) if tong_he_so > 0 else 0.0


def xep_loai(dtb):

    if dtb >= 8.0: return "Tốt"

    elif dtb >= 6.5: return "Khá"

    elif dtb >= 5.0: return "Đạt"

    else: return "Chưa đạt"


def them_hoc_sinh(danh_sach_lop):

    ma_hs = input("Nhập mã HS: ").strip()

    for hs in danh_sach_lop:

        if hs["ma_hs"] == ma_hs:

            print("Lỗi: Mã HS đã tồn tại!")

            return

    ho_ten = input("Nhập họ tên: ").strip()

    danh_sach_lop.append({"ma_hs": ma_hs, "ho_ten": ho_ten, "diem": {}})

    print("Thêm thành công!")

[Bước 4] Quản lý và Lưu trữ dữ liệu

Tích hợp ghi/đọc tập tin CSV bằng module `csv` và xử lý ngoại lệ `FileNotFoundError`:


import csv

import os


FILE_PATH = "bang_diem_lop7.csv"


def luu_file_csv(danh_sach_lop):

    try:

        with open(FILE_PATH, mode="w", newline="", encoding="utf-8") as f:

            writer = csv.writer(f)

            writer.writerow(["ma_hs", "ho_ten", "mon", "tx", "gk", "ck"])

            for hs in danh_sach_lop:

                if not hs["diem"]:

                    writer.writerow([hs["ma_hs"], hs["ho_ten"], "", "", "", ""])

                else:

                    for mon, d in hs["diem"].items():

                        tx_str = ";".join(map(str, d.get("tx", [])))

                        writer.writerow([hs["ma_hs"], hs["ho_ten"], mon, tx_str, d.get("gk", ""), d.get("ck", "")])

        print("Đã lưu dữ liệu vào đĩa cứng!")

    except Exception as e:

        print(f"Lỗi lưu file: {e}")


def doc_file_csv():

    danh_sach_lop = []

    if not os.path.exists(FILE_PATH):

        return danh_sach_lop

    try:

        with open(FILE_PATH, mode="r", encoding="utf-8") as f:

            reader = csv.DictReader(f)

            students = {}

            for row in reader:

                ma = row["ma_hs"]

                if ma not in students:

                    students[ma] = {"ma_hs": ma, "ho_ten": row["ho_ten"], "diem": {}}

                if row["mon"]:

                    tx = [float(x) for x in row["tx"].split(";") if x]

                    gk = float(row["gk"]) if row["gk"] else 0.0

                    ck = float(row["ck"]) if row["ck"] else 0.0

                    students[ma]["diem"][row["mon"]] = {"tx": tx, "gk": gk, "ck": ck}

            return list(students.values())

    except FileNotFoundError:

        return []

[Bước 5] Kiểm thử và Tối ưu mã nguồn

Kịch bản kiểm thử (Test cases):

- Nhập chuỗi chữ vào ô điểm > Hàm nhap_diem_hop_le()  bẫy lỗi ValueError và yêu cầu nhập lại

- Chạy ứng dụng lần đầu tiên khi chưa có tập tin bang_diem_lop7.csv > Khối try...except sẽ bẫy lỗi này và trả về danh sách rỗng thay vì thông báo lỗi hoặc treo chương trình

Tối ưu mã nguồn:

- Bổ sung tài liệu (docstring) mô tả tham số và giá trị trả về cho các hàm (`tinh_dtb_mon`, `luu_file_csv`)

- Chuẩn hóa việc đặt tên biến theo quy tắc snake_case (ví dụ: danh_sach_lop, tinh_dtb_mon)

-----