--------------- <> -----------------
--- 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 (28) - Dự án kết thúc giai đoạn 1 (2)

Bài trước: Python (27) - Dự án kết thúc giai đoạn 1 (1)
-----

27.3.2 Hướng dẫn làm từng bước Dự án mẫu

Tạo thư mục, tập tin cho dự án

- Tạo thư mục mới, đặt tên theo chuẩn Pascal, tiếng Việt, không dấu, ví dụ QuanLyBangDiemLop7

- Trong thư mục QuanLyBangDiemLop7, tạo tập tin mã nguồn của dự án, ví dụ main.py

Lập trình hàm main()

- Trong main.py, viết hàm main() và chạy thử

- Bạn nên viết mã nguồn bằng IDLE hoặc Notepad để không có gợi ý của trình viết mã, giúp bạn học được nhiều hơn. Sau này, biết nhiều rồi thì mới nên dùng VS Code hoặc một IDE bất kỳ.

[main.py]

def main():

    print("hi")


if __name__ == "__main__":

    main()

- Bạn nên hỏi AI về lệnh if ở đoạn mã trên, để hiểu cách Python gọi hàm main() khi thực thi. Có thể hiểu đơn giản: __name__ là biến ẩn của Python, nó sẽ được gán giá trị là __main__ khi bạn chạy trực tiếp một tập tin .py. Và khi đó, Python sẽ sẽ gọi hàm main() để thực thi.

- Chạy thử đoạn mã trên, bạn sẽ thấy chữ “hi” xuất hiện trên màn hình kết quả, vậy là bạn đã sẵn sàng để lập trình

- Cho xuất hiện menu của chương trình:

[main.py]

def main():

    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")

        # sau này bạn sẽ thêm các chức năng ở đây

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


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

        if lua_chon == "1":

            print("Danh sách lớp & bảng điểm")

        elif lua_chon == 2:

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


        elif lua_chon == "0":

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

            break

        else:

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


if __name__ == "__main__":

    main()

- Bạn chạy thử chương trình, nhập các số khác nhau để kiểm tra xem chương trình chạy đúng chưa

Tạo tập tin lưu danh sách lớp

- Việc đầu tiên chúng ta cần làm là: trong thư mục dự án, tạo tập tin để lưu danh sách lớp và điểm của mỗi bạn (đặt tên là bang_diem_lop7.csv)

Tập tin bang_diem_lop7.csv được định dạng theo chuẩn csv:

- Hàng đầu tiên là tiêu đề

- Các hàng tiếp theo là thông tin của học sinh và điểm, mỗi học sinh sẽ có nhiều hàng, mỗi hàng gồm nhiều trường, mỗi trường sẽ cách nhau bằng dấu phẩy (,). Riêng trường điểm thường xuyên (tx), vì có nhiều, nên sẽ dùng dấu chấm phẩy (;) để ngăn cách

Ví dụ:

ma_hs,ho_ten,mon,tx,gk,ck 

HS01,Nguyễn Văn Tèo,Toán,8.0;7.0,7.5,9.0 

HS01,Nguyễn Văn Tèo,Ngữ văn,7.0;6.0,7.0,7.5  

Ở bảng dữ liệu trên, bạn Tèo sẽ có mã số là HS01, có dòng điểm cho môn Toán gồm 2 điểm thường xuyên là 8.0 và 7.0, điểm giữa kỳ (gk) là 7.5 và điểm cuối kỳ (ck) là 9. Và dòng điểm cho môn Ngữ văn.

Bạn hãy chèn nội dung trên vào tập tin  bang_diem_lop7.csv

[bang_diem_lop7.csv]

ma_hs,ho_ten,mon,tx,gk,ck 

HS01,Nguyễn Văn Tèo,Toán,8.0;7.0,7.5,9.0 

HS01,Nguyễn Văn Tèo,Ngữ văn,7.0;6.0,7.0,7.5  

Viết hàm đọc dữ liệu từ tập tin

- Để đọc dữ liệu từ tập tin CSV, cần sử dụng thư viện csv. Chúng ta sẽ thêm lệnh gọi thư viện (import csv) vào đầu tập tin main.py. Đồng thời, chúng ta cũng sử dụng thư viện os để xử lý tập tin. Khai báo hằng FILE_PATH để tiện khi lập trình.

[main.py]

import csv

import os


FILE_PATH = "bang_diem_lop7.csv"

def main():

...

- Trong tập tin main.py, sau hàm def main(), tạo hàm doc_file_csv(), với nội dung sau: sử dụng cấu trúc try…except để bẫy lỗi FileNotFoundError.

[main.py]

        else:

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


# hàm đọc tập tin csv

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)

            print("trong doc_file")

            print(reader)

# bạn nên chèn các đoạn mã, để kiểm tra chạy từng bước


    except FileNotFoundError:

        return[]

            

if __name__ == "__main__":

    main()

- Gọi hàm doc_file_csv() trong hàm main(), để chạy và kiểm tra:

 if lua_chon == "1":

            doc_file_csv()

        elif lua_chon == 2:

- Tiếp tục hoàn thành hàm doc_file_csv()

# hàm đọc tập tin csv

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())

- Thêm một đoạn mã nhỏ trong hàm main(), để kiểm tra kết quả chạy của hàm doc_file_csv():

if lua_chon == "1":

            danh_sach_hs = doc_file_csv()

            print(danh_sach_hs)

        elif lua_chon == 2:

Nếu chạy được, chương trình sẽ xuất ra  kết quả là:

[{'ma_hs': 'HS01', 'ho_ten': 'Nguyễn Văn Tèo', 'diem': {'Toán': {'tx': [8.0, 7.0], 'gk': 7.5, 'ck': 9.0}, 'Ngữ văn': {'tx': [7.0, 6.0], 'gk': 7.0, 'ck': 7.5}}}]

...

Tuy nhiên, chức năng số 1 là: hiển thị Danh sách và bảng điểm.

Danh sách và bảng điểm sẽ có dạng như sau:

Tương ứng với nội dung dữ liệu trong tập tin bang_diem_lop7.csv có dạng như sau:

ma_hs,ho_ten,mon,tx,gk,ck

HS001,Nguyễn Văn Tèo,Toán,8.0;7.0,7.5,9.0

HS001,Nguyễn Văn Tèo,Ngữ văn,7.0;6.0,7.0,7.5

HS001,Nguyễn Văn Tèo,Tiếng Anh,9.0;10.0,9.5,9.0

HS002,Trần Thị Tí,Toán,6.0;6.5,6.0,7.0

HS002,Trần Thị Tí,Lịch sử_Địa lý,8.0;8.5,8.0,8.5

HS003,Lê Sửu,,,

Hàm doc_file_csv() đã trả về một List chứa thông tin của tất cả học sinh, cùng với các điểm thành phần. Chúng ta sẽ viết tiếp hàm hien_thi_bang_diem(danh_sach_lop) để hiển thị Danh sách lớp và bảng điểm.

Vì hàm hien_thi_bang_diem(danh_sach_lop) có dùng tới biến danh_sach_lop, nên chúng ta khai báo và gọi hàm doc_file_csv() để có dữ liệu. Đồng thời gọi hàm hien_thi_bang_diem(danh_sach_lop) trong mục chọn 1.

[main.py]

def main():

    danh_sach_lop = doc_file_csv()

    while True:

...

 if lua_chon == "1":

            hien_thi_bang_diem(danh_sach_lop)

        elif lua_chon == 2:

...

Viết hàm  hien_thi_bang_diem(danh_sach_lop) ngay phía dưới hàm doc_file_csv().

[main.py]

    except FileNotFoundError:

        return[]


# hàm hiển thị danh sách và bảng điểm

def hien_thi_bang_diem(danh_sach_lop):

    """Hiển thị Danh sách và bảng điểm"""

    if not danh_sach_lop:

        print("\n Danh sách không có học sinh nào!")

        return

    print("\n" + "=" * 65)

    print(f"{'MÃ HS':<10}{'HỌ VÀ TÊN':<20}{'MÔN':<15}{'ĐTB MÔN':<10}{'XẾP LOẠI'}")

    print("=" * 65)

    for hs in danh_sach_lop:

        print(f"{hs['ma_hs']:<10}{hs['ho_ten']:<20}")

        if not hs["diem"]:

            print(f"{'':<30}(Chưa có điểm)")

        else:

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

                #dtb = tinh_dtb_mon(d) - sẽ viết sau

                #xl = xep_loai(dtb) - sẽ viết sau

                dtb = 5.0

                xl = "Trung bình"

                print(f"{'':<10}{'':<20}{mon:<15}{dtb:<10}{xl}")

        print("-" * 65)

    

if __name__ == "__main__":

- Chạy chương trình để xem kết quả:


-----
Bài sau:

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)

-----