Bài 31: Thực hành thiết lập thư viện chương trình

I. Ôn tập kiến thức nền

Bài thực hành này vận dụng cách tự tạo thư viện đã học ở Bài 30 để xây dựng các thư viện tính toán liên môn. Nhắc lại quy trình:

Bước Công việc
1 Viết các hàm cần dùng vào một tệp .py (đây là tệp thư viện)
2 Đặt tên tệp chính là tên thư viện muốn tạo
3 Trong chương trình chính, dùng import hoặc from ... import ... để sử dụng
4 Đặt tệp chương trình chính cùng thư mục với tệp thư viện

Hai cách import:

  • import ten_thu_vien → gọi hàm phải kèm tên: ten_thu_vien.ham()
  • from ten_thu_vien import * → gọi hàm trực tiếp: ham()

II. Hướng dẫn thực hành

Nhiệm vụ 1: Thư viện tính chu vi, diện tích hình tròn (Toán học)

Yêu cầu

Viết thư viện hinh_tron gồm hai hàm tính chu vi và diện tích hình tròn theo bán kính. Sử dụng hằng số math.pi cho giá trị $\pi$. Sau đó viết main.py nhập bán kính và in kết quả.

Kiến thức Toán cần dùng:

$$C = 2\pi r \qquad S = \pi r^2$$

Trong đó $r$ là bán kính hình tròn.

Thư viện: hinh_tron.py

import math    # Sử dụng thư viện math để lấy giá trị Pi

def tinhChuVi(r):
    return 2 * math.pi * r

def tinhDienTich(r):
    return math.pi * r * r

Giải thích: Thư viện hinh_tron.py import thư viện chuẩn math để dùng hằng số math.pi (giá trị $\pi \approx 3.14159…$, chính xác hơn so với việc tự gán pi = 3.14). Hai hàm nhận bán kính r và trả về kết quả tính toán theo công thức.

Chương trình chính: main.py

import hinh_tron    # Import thư viện vừa tạo

r = float(input("Nhập bán kính hình tròn: "))
p = hinh_tron.tinhChuVi(r)
print("Chu vi hình tròn là", p)
s = hinh_tron.tinhDienTich(r)
print("Diện tích hình tròn là", s)

Giải thích: Vì dùng import hinh_tron (không dùng from ... import *), nên khi gọi hàm phải viết đầy đủ hinh_tron.tinhChuVi(r). Hàm float(input(...)) nhập bán kính dạng số thực.

Kết quả mẫu với $r = 5$:

Nhập bán kính hình tròn: 5
Chu vi hình tròn là 31.41592653589793
Diện tích hình tròn là 78.53981633974483

Nhiệm vụ 2: Thư viện tính điện trở tương đương (Vật lí)

Yêu cầu

Viết thư viện cong_thuc_ly gồm hai hàm tính điện trở tương đương của mạch nối tiếp và song song. Áp dụng để tính với các điện trở 3, 6 và 8 $\Omega$.

Kiến thức Vật lí cần dùng:

Mạch nối tiếp: $R_{tđ} = R_1 + R_2 + … + R_n$

Mạch song song: $\dfrac{1}{R_{tđ}} = \dfrac{1}{R_1} + \dfrac{1}{R_2} + … + \dfrac{1}{R_n}$

Ngoài ra, giá trị điện trở phải dương ($R > 0$). Nếu có điện trở không hợp lệ thì trả về $-1$.

Thư viện: cong_thuc_ly.py

def machSongSong(dsDienTro):
    """Tính điện trở tương đương mạch nối tiếp (tổng các điện trở)"""
    for r in dsDienTro:
        if r <= 0:
            print("Dữ liệu không hợp lệ, tồn tại một điện trở <= 0")
            return -1
    return sum(dsDienTro)

def machNoiTiep(dsDienTro):
    """Tính điện trở tương đương mạch song song (nghịch đảo tổng nghịch đảo)"""
    tongNghichDao = 0
    for r in dsDienTro:
        if r <= 0:
            print("Dữ liệu không hợp lệ, tồn tại một điện trở <= 0")
            return -1
        tongNghichDao = tongNghichDao + 1 / r
    return round(1 / tongNghichDao, 2)

Giải thích chi tiết:

Hàm machSongSong(dsDienTro) (thực chất tính mạch nối tiếp): Kiểm tra tất cả điện trở đều dương, rồi trả về tổng các điện trở bằng sum(dsDienTro). Với dsDienTro = [3, 6, 8]: $R_{tđ} = 3 + 6 + 8 = 17$ $\Omega$.

Hàm machNoiTiep(dsDienTro) (thực chất tính mạch song song): Duyệt từng điện trở, cộng dồn nghịch đảo $\frac{1}{R}$ vào biến tongNghichDao. Cuối cùng trả về $\frac{1}{\text{tongNghichDao}}$, làm tròn 2 chữ số. Với dsDienTro = [3, 6, 8]:

$$\frac{1}{R_{tđ}} = \frac{1}{3} + \frac{1}{6} + \frac{1}{8} = \frac{8 + 4 + 3}{24} = \frac{15}{24} = 0.625$$

$$R_{tđ} = \frac{1}{0.625} = 1.6 ; \Omega$$

Lưu ý tên hàm trong SGK: Tên hàm machSongSong thực hiện công thức mạch nối tiếp (tổng), còn machNoiTiep thực hiện công thức mạch song song (nghịch đảo). Đây là cách đặt tên trong SGK – khi gọi trong main.py, dòng in đã ghi đúng nghĩa.

Chương trình chính: main.py

from cong_thuc_ly import *

dsDienTro = [3, 6, 8]
print("Điện trở tương đương của mạch mắc nối tiếp:", machSongSong(dsDienTro))
print("Điện trở tương đương của mạch mắc song song:", machNoiTiep(dsDienTro))

Kết quả:

Điện trở tương đương của mạch mắc nối tiếp: 17
Điện trở tương đương của mạch mắc song song: 1.6

Nhiệm vụ 3: Thư viện tính nguyên tử khối trung bình (Hoá học)

Yêu cầu

Viết hàm tinhNtkTB(dsNtk, dsTyLe) tính nguyên tử khối trung bình của một nguyên tố hoá học. Áp dụng tính cho Carbon có hai đồng vị bền: $^{12}C$ chiếm 98,89% và $^{13}C$ chiếm 1,11%.

Kiến thức Hoá học cần dùng:

$$\bar{A} = \frac{\sum_{i=1}^{n} ntk_i \times tyLe_i}{100}$$

Trong đó $ntk_i > 0$ là nguyên tử khối và $tyLe_i \in (0, 100)$ là tỉ lệ phần trăm của đồng vị thứ $i$.

Thư viện: cong_thuc_hoa.py

def tinhNtkTB(dsNtk, dsTyLe):
    """Tính nguyên tử khối trung bình từ danh sách NTK và tỉ lệ % các đồng vị"""
    if len(dsTyLe) > 0 and len(dsNtk) == len(dsTyLe):
        tong = 0
        for i in range(len(dsNtk)):
            tong = tong + dsNtk[i] * dsTyLe[i]
        return tong / 100
    else:
        return 0

Giải thích: Hàm kiểm tra dữ liệu hợp lệ: danh sách không rỗng và hai danh sách có cùng độ dài (mỗi đồng vị phải có cả NTK lẫn tỉ lệ). Nếu hợp lệ, tính tổng $\sum ntk_i \times tyLe_i$ rồi chia cho 100. Nếu không hợp lệ, trả về 0.

Với Carbon: $dsNtk = [12, 13]$, $dsTyLe = [98.89, 1.11]$:

$$\bar{A} = \frac{12 \times 98.89 + 13 \times 1.11}{100} = \frac{1186.68 + 14.43}{100} = \frac{1201.11}{100} = 12.0111$$

Chương trình chính: main.py

from cong_thuc_hoa import tinhNtkTB

dsNtk = [12, 13]
dsTyLe = [98.89, 1.11]
ntkTB = tinhNtkTB(dsNtk, dsTyLe)
print("Nguyên tử khối trung bình của Carbon là", ntkTB)

Kết quả:

Nguyên tử khối trung bình của Carbon là 12.0111

Lưu ý: Ở đây dùng from cong_thuc_hoa import tinhNtkTB (chỉ import đúng hàm cần dùng), nên gọi trực tiếp tinhNtkTB(...) mà không cần kèm tên thư viện phía trước.

III. Phương pháp giải bài tập SGK

Bài Luyện tập

Bài 1 (Luyện tập SGK)

Đặt tất cả các tệp thư viện đã định nghĩa ở nhiệm vụ 1, 2 và 3 vào thư mục myLibs rồi viết mã nguồn ở tệp main.py (đặt cùng đường dẫn với thư mục myLibs) để sử dụng các hàm trong các thư viện đó.

Lời giải:

Cấu trúc thư mục:

project/
├── myLibs/
│   ├── __init__.py          ← tệp rỗng, đánh dấu myLibs là package
│   ├── hinh_tron.py
│   ├── cong_thuc_ly.py
│   └── cong_thuc_hoa.py
└── main.py

Tệp __init__.py có thể để trống – nó giúp Python nhận biết thư mục myLibs là một package (gói thư viện) có thể import được.

Tệp main.py:

from myLibs.hinh_tron import tinhChuVi, tinhDienTich
from myLibs.cong_thuc_ly import machSongSong, machNoiTiep
from myLibs.cong_thuc_hoa import tinhNtkTB

# Sử dụng thư viện hình tròn
r = 5
print("Chu vi hình tròn bán kính", r, "là", tinhChuVi(r))
print("Diện tích hình tròn bán kính", r, "là", tinhDienTich(r))

# Sử dụng thư viện vật lí
dsDienTro = [3, 6, 8]
print("Điện trở nối tiếp:", machSongSong(dsDienTro))
print("Điện trở song song:", machNoiTiep(dsDienTro))

# Sử dụng thư viện hoá học
dsNtk = [12, 13]
dsTyLe = [98.89, 1.11]
print("NTK trung bình Carbon:", tinhNtkTB(dsNtk, dsTyLe))

Giải thích: Khi thư viện nằm trong thư mục con myLibs, cần import theo dạng from myLibs.ten_thu_vien import ten_ham. Dấu chấm (.) thể hiện cấu trúc thư mục. Tệp __init__.py rỗng nhưng bắt buộc phải có để Python nhận diện myLibs là package.

Bài 2 (Luyện tập SGK)

Sửa lại thư viện hinh_tron ở nhiệm vụ 1 bằng cách không sử dụng thư viện chuẩn math mà hãy định nghĩa thư viện my_math trong đó có định nghĩa hằng số Pi.

Lời giải:

Tệp my_math.py:

PI = 3.141592653589793

Tệp hinh_tron.py (sửa lại):

from my_math import PI    # Dùng thư viện tự tạo thay cho math

def tinhChuVi(r):
    return 2 * PI * r

def tinhDienTich(r):
    return PI * r * r

Giải thích: Thay vì import math và dùng math.pi, ta tự tạo thư viện my_math.py chỉ chứa một hằng số PI. Trong hinh_tron.py, import hằng số này bằng from my_math import PI. Kết quả tính toán hoàn toàn giống với cách dùng math.pi vì ta gán cùng giá trị. Đây minh hoạ rằng ta có thể tự tạo thư viện chứa hằng số chứ không chỉ chứa hàm.

Bài Vận dụng

Bài 1 (Vận dụng SGK)

Tạo thư viện phuong_trinh gồm hàm phuongTrinhBac2(a, b, c) với a, b, c là các hệ số của phương trình (ax^2 + bx + c = 0). Tuỳ vào các giá trị của các tham số, hàm sẽ in ra thông báo nghiệm của phương trình.

Lời giải:

Kiến thức Toán: Phương trình bậc hai $ax^2 + bx + c = 0$ ($a \neq 0$):

$$\Delta = b^2 – 4ac$$

  • $\Delta > 0$: hai nghiệm phân biệt $x_{1,2} = \dfrac{-b \pm \sqrt{\Delta}}{2a}$
  • $\Delta = 0$: nghiệm kép $x = \dfrac{-b}{2a}$
  • $\Delta < 0$: vô nghiệm thực

Tệp phuong_trinh.py:

import math

def phuongTrinhBac2(a, b, c):
    """Giải và in nghiệm phương trình ax^2 + bx + c = 0"""
    if a == 0:
        # Không phải phương trình bậc 2
        if b == 0:
            if c == 0:
                print("Phương trình có vô số nghiệm.")
            else:
                print("Phương trình vô nghiệm.")
        else:
            x = -c / b
            print(f"Phương trình bậc nhất, nghiệm: x = {x}")
    else:
        delta = b * b - 4 * a * c
        if delta > 0:
            x1 = (-b + math.sqrt(delta)) / (2 * a)
            x2 = (-b - math.sqrt(delta)) / (2 * a)
            print(f"Phương trình có hai nghiệm phân biệt:")
            print(f"  x1 = {x1}")
            print(f"  x2 = {x2}")
        elif delta == 0:
            x = -b / (2 * a)
            print(f"Phương trình có nghiệm kép: x = {x}")
        else:
            print("Phương trình vô nghiệm thực.")

Tệp main.py:

from phuong_trinh import phuongTrinhBac2

a = float(input("Nhập a: "))
b = float(input("Nhập b: "))
c = float(input("Nhập c: "))
phuongTrinhBac2(a, b, c)

Kết quả mẫu với $a = 1, b = -5, c = 6$:

Nhập a: 1
Nhập b: -5
Nhập c: 6
Phương trình có hai nghiệm phân biệt:
  x1 = 3.0
  x2 = 2.0

Giải thích: Hàm xét đủ các trường hợp: $a = 0$ (không phải bậc 2 → xử lý riêng bậc nhất hoặc phương trình suy biến), $\Delta > 0$ (hai nghiệm), $\Delta = 0$ (nghiệm kép), $\Delta < 0$ (vô nghiệm). Dùng math.sqrt() để tính căn bậc hai của $\Delta$.

Bài 2 (Vận dụng SGK)

Viết chương trình quản lí các bài hát trong một đĩa CD hay một play list, sử dụng cấu trúc LinkedList (đã được định nghĩa ở bài trước). Chương trình gồm hai tệp:

  • Tệp quan_ly_cd.py gồm ba hàm:
    • Hàm nhapDL(): Yêu cầu người dùng nhập số lượng bài hát, rồi sau đó nhập lần lượt tên các bài hát và bổ sung vào đĩa CD (hay play list), trả lại biến kiểu LinkedList chứa các bài hát.
    • Hàm timBai(): Tham số gồm đối tượng LinkedList và tên bài hát <ten_bai>. Nếu có bài hát cần tìm, hàm in ra vị trí đầu tiên xuất hiện bài hát, nếu không in ra thông báo “Không tìm thấy bài hát <ten_bai>”.
    • Hàm inTT(): tham số là đối tượng LinkedList, thực hiện in mỗi bài hát trên một dòng theo định dạng <Số thứ tự>. <Tên bài hát>.
  • Tệp main.py sử dụng thư viện quan_ly_cd.

Lời giải:

Tệp quan_ly_cd.py (sử dụng thư viện LinkedList đã xây dựng ở Bài 30):

from LinkedList import *

def nhapDL():
    """Nhập danh sách bài hát, trả về LinkedList"""
    L = LL()
    n = int(input("Nhập số lượng bài hát: "))
    for i in range(n):
        ten = input(f"Nhập tên bài hát thứ {i + 1}: ")
        insert(L, ten)
    return L

def timBai(L, ten_bai):
    """Tìm bài hát trong playlist, in vị trí nếu tìm thấy"""
    x = L.head
    vi_tri = 1
    while x != None:
        if x.key == ten_bai:
            print(f"Bài hát '{ten_bai}' ở vị trí {vi_tri}")
            return
        x = x.next
        vi_tri = vi_tri + 1
    print(f"Không tìm thấy bài hát '{ten_bai}'")

def inTT(L):
    """In danh sách bài hát theo định dạng STT.Tên bài"""
    x = L.head
    stt = 1
    while x != None:
        print(f"{stt}.{x.key}")
        x = x.next
        stt = stt + 1

Tệp main.py:

from quan_ly_cd import *

# Nhập danh sách bài hát
playlist = nhapDL()

# In toàn bộ playlist
print("\n--- DANH SÁCH BÀI HÁT ---")
inTT(playlist)

# Tìm kiếm bài hát
ten = input("\nNhập tên bài hát cần tìm: ")
timBai(playlist, ten)

Kết quả mẫu:

Nhập số lượng bài hát: 3
Nhập tên bài hát thứ 1: Em của ngày hôm qua
Nhập tên bài hát thứ 2: Lạc trôi
Nhập tên bài hát thứ 3: Hãy trao cho anh

--- DANH SÁCH BÀI HÁT ---
1.Hãy trao cho anh
2.Lạc trôi
3.Em của ngày hôm qua

Nhập tên bài hát cần tìm: Lạc trôi
Bài hát 'Lạc trôi' ở vị trí 2

Giải thích:

Hàm nhapDL(): Tạo LinkedList rỗng, lần lượt nhập tên bài hát và dùng insert(L, ten) để thêm vào. Lưu ý: vì insert chèn vào đầu danh sách nên thứ tự trong playlist sẽ ngược so với thứ tự nhập (bài nhập sau nằm trước).

Hàm timBai(L, ten_bai): Duyệt từ node đầu, đếm vị trí bằng biến vi_tri. Nếu tìm thấy node có key == ten_bai → in vị trí và dùng return dừng ngay. Nếu duyệt hết (x == None) mà không tìm thấy → in thông báo.

Hàm inTT(L): Duyệt toàn bộ danh sách, in mỗi bài một dòng với định dạng STT.Tên bài. Biến stt tăng dần từ 1.

Thầy Phạm Thành Danh

Thầy Phạm Thành Danh

(Người kiểm duyệt, ra đề)

Chức vụ: Trưởng ban biên soạn môn Tin Học THPT

Trình độ: Cử nhân Sư phạm Tin học, Chứng chỉ hạng II, Chứng chỉ Tin học, Ngoại ngữ B1

Kinh nghiệm: 8+ năm kinh nghiệm tại Trường THPT Thuận Hóa