M10 — Hàm (Function)

← 09 - Dict và Set | Tiếp theo → 11 - Xử lý lỗi (Exception)

Mục tiêu module

  • Định nghĩa và gọi hàm bằng def
  • Hiểu parameter (tham số) vs argument (đối số)
  • Nắm tham số mặc định, keyword argument, *args, **kwargs
  • Hiểu scope (phạm vi biến) và tránh bẫy mutable default
  • Viết docstring đúng chuẩn

Thời lượng: ~9 giờ | Tuần: 16–17

Tuần 16: Mục 1–7 | Tuần 17: Mục 8–13


PHẦN 1 — TUẦN 16

1. Vì sao cần hàm?

Xem lại chương trình tính hóa đơn ở 06 - Cấu trúc rẽ nhánh (if-elif-else). Nếu cần tính hóa đơn cho 5 khách hàng, bạn phải copy đoạn code đó 5 lần. Sửa công thức thuế → phải sửa 5 chỗ.

Function (hàm) giải quyết vấn đề này: viết một lần, gọi nhiều lần.

Ba lợi ích

Lợi íchGiải thích
Tái sử dụng (reusability)Viết một lần, gọi ở mọi nơi
Dễ bảo trì (maintainability)Sửa logic chỉ ở một chỗ
Dễ đọc (readability)Chia bài toán lớn thành các việc nhỏ có tên gọi rõ ràng

Liên hệ công việc của bạn

Hàm trong Python đóng vai trò giống Stored Procedure và User-Defined Function trong SQL Server: đóng gói một logic nghiệp vụ, nhận tham số vào, trả kết quả ra. Điểm khác: hàm Python nhẹ hơn nhiều, bạn sẽ viết hàng chục hàm trong một file là chuyện bình thường.


2. Định nghĩa và gọi hàm

2.1. Cú pháp

def <tên_hàm>(<tham số>):
    """Docstring — mô tả chức năng."""
    <thân hàm>
    return <giá trị trả về>
def chao_hoi():
    """In lời chào."""
    print("Xin chao!")
    print("Chuc mot ngay tot lanh")
 
# Gọi hàm
chao_hoi()
chao_hoi()
Xin chao!
Chuc mot ngay tot lanh
Xin chao!
Chuc mot ngay tot lanh

2.2. Định nghĩa phải đứng TRƯỚC lời gọi

chao_hoi()          # ❌ NameError: name 'chao_hoi' is not defined
 
def chao_hoi():
    print("Xin chao")

Python đọc file từ trên xuống. Tại thời điểm gọi, hàm chưa được định nghĩa.

Cấu trúc file chuẩn:

"""Mô tả chương trình."""
 
# 1. Import
# 2. Hằng số
# 3. Định nghĩa các hàm
def ham_1():
    ...
 
def ham_2():
    ...
 
# 4. Code chính (gọi các hàm)
ham_1()
ham_2()

3. Tham số và đối số

def chao_hoi(ten):              # ten là PARAMETER (tham số)
    print(f"Xin chao {ten}!")
 
chao_hoi("Nguyen Van A")        # "Nguyen Van A" là ARGUMENT (đối số)
chao_hoi("Tran Thi B")
Thuật ngữĐịnh nghĩa
Parameter (tham số)Tên biến trong định nghĩa hàm
Argument (đối số)Giá trị thực truyền vào khi gọi hàm

3.1. Nhiều tham số

def tinh_thanh_tien(so_luong, don_gia):
    """Tính thành tiền = số lượng × đơn giá."""
    return so_luong * don_gia
 
print(tinh_thanh_tien(3, 2_500_000))     # 7500000

3.2. Số lượng đối số phải khớp

def cong(a, b):
    return a + b
 
cong(1)              # ❌ TypeError: cong() missing 1 required positional argument: 'b'
cong(1, 2, 3)        # ❌ TypeError: cong() takes 2 positional arguments but 3 were given

3.3. Positional vs Keyword argument

def tao_hoa_don(ma_kh, ngay, so_tien):
    print(f"HD cho {ma_kh} ngay {ngay}: {so_tien:,}")
 
# Positional — theo THỨ TỰ
tao_hoa_don("KH001", "08/08/2026", 5_000_000)
 
# Keyword — theo TÊN, thứ tự tùy ý
tao_hoa_don(so_tien=5_000_000, ma_kh="KH001", ngay="08/08/2026")
 
# Kết hợp — positional phải đứng TRƯỚC
tao_hoa_don("KH001", so_tien=5_000_000, ngay="08/08/2026")
 
# ❌ Sai — positional sau keyword
tao_hoa_don(ma_kh="KH001", "08/08/2026", 5_000_000)   # SyntaxError

Khi nào dùng keyword argument?

Khi hàm có nhiều tham số, keyword argument làm code dễ đọc hơn nhiều:

tinh_luong(25_000_000, 26, 22, 2_000_000, True)          # ❌ Khó hiểu
tinh_luong(luong_co_ban=25_000_000, ngay_chuan=26,
           ngay_cong=22, phu_cap=2_000_000, co_bhxh=True)  # ✅ Rõ ràng

4. return — Trả về giá trị

4.1. Khác biệt giữa print và return

def cong_in(a, b):
    print(a + b)            # CHỈ in ra màn hình
 
def cong_tra_ve(a, b):
    return a + b            # TRẢ VỀ giá trị để dùng tiếp
 
x = cong_in(1, 2)           # In ra: 3
print(x)                    # None  <- không trả về gì
 
y = cong_tra_ve(1, 2)       # Không in gì
print(y)                    # 3
print(y * 10)               # 30    <- dùng tiếp được

Lỗi phổ biến nhất của người mới

Viết print() trong hàm thay vì return. Hàm chỉ print thì không tái sử dụng được — bạn không thể lấy kết quả để tính tiếp.

Nguyên tắc: Hàm tính toán thì return. Hàm hiển thị thì print. Tách riêng hai loại này.

# ✅ Tách riêng
def tinh_thanh_tien(sl, gia):
    """Tính toán — trả về số."""
    return sl * gia
 
def in_dong_hoa_don(ten, sl, gia):
    """Hiển thị — in ra màn hình."""
    tt = tinh_thanh_tien(sl, gia)
    print(f"{ten:<25}{sl:>5}{gia:>14,}{tt:>16,}")

4.2. return kết thúc hàm ngay lập tức

def kiem_tra(x):
    if x < 0:
        return "So am"
    if x == 0:
        return "So khong"
    return "So duong"
 
print(kiem_tra(-5))     # So am

Không cần else vì return đã thoát khỏi hàm. Đây là guard clause đã nhắc ở 06 - Cấu trúc rẽ nhánh (if-elif-else) — giờ mới dùng được đầy đủ sức mạnh:

# ❌ Lồng sâu
def xu_ly(du_lieu):
    if du_lieu is not None:
        if len(du_lieu) > 0:
            if du_lieu[0] == "OK":
                return "Thanh cong"
            else:
                return "Trang thai sai"
        else:
            return "Rong"
    else:
        return "Khong co du lieu"
 
# ✅ Guard clause — phẳng, dễ đọc
def xu_ly(du_lieu):
    if du_lieu is None:
        return "Khong co du lieu"
    if len(du_lieu) == 0:
        return "Rong"
    if du_lieu[0] != "OK":
        return "Trang thai sai"
    return "Thanh cong"

4.3. Hàm không có return trả về None

def in_chao():
    print("Xin chao")
 
kq = in_chao()
print(kq)               # None

4.4. Trả về nhiều giá trị

def phan_tich(ds):
    """Trả về tổng, trung bình, min, max của danh sách."""
    return sum(ds), sum(ds) / len(ds), min(ds), max(ds)
 
tong, tb, nho_nhat, lon_nhat = phan_tich([10, 20, 30, 40])
print(tong, tb, nho_nhat, lon_nhat)     # 100 25.0 10 40
 
# Thực chất Python trả về một tuple
kq = phan_tich([10, 20, 30, 40])
print(kq)               # (100, 25.0, 10, 40)
print(type(kq))         # <class 'tuple'>

Trả về dict khi có nhiều giá trị và cần đặt tên rõ ràng:

def phan_tich(ds):
    return {
        "tong": sum(ds),
        "trung_binh": sum(ds) / len(ds),
        "min": min(ds),
        "max": max(ds),
        "so_luong": len(ds),
    }
 
kq = phan_tich([10, 20, 30, 40])
print(kq["trung_binh"])     # 25.0

5. Tham số mặc định (Default Parameter)

def tinh_thue(so_tien, thue_suat=0.1):
    """Tính tiền thuế. Thuế suất mặc định 10%."""
    return so_tien * thue_suat
 
print(tinh_thue(1_000_000))          # 100000.0 — dùng mặc định
print(tinh_thue(1_000_000, 0.05))    # 50000.0  — ghi đè
print(tinh_thue(1_000_000, thue_suat=0.08))   # 80000.0

5.1. Tham số có mặc định phải đứng SAU

def f(a, b=2, c=3):        # ✅
    ...
 
def f(a=1, b, c):          # ❌ SyntaxError: non-default argument follows default argument
    ...

5.2. ⚠️ BẪY: Mutable default argument

Đây là bẫy nổi tiếng nhất của Python.

# ❌ SAI
def them_muc(muc, danh_sach=[]):
    danh_sach.append(muc)
    return danh_sach
 
print(them_muc("A"))      # ['A']
print(them_muc("B"))      # ['A', 'B']  <- ??? Tại sao còn 'A'?
print(them_muc("C"))      # ['A', 'B', 'C']

Nguyên nhân: Giá trị mặc định được tạo MỘT LẦN DUY NHẤT khi định nghĩa hàm, không phải mỗi lần gọi. Vì list là mutable, mọi lời gọi đều dùng chung một list.

Cách sửa — dùng None làm mặc định:

# ✅ ĐÚNG
def them_muc(muc, danh_sach=None):
    if danh_sach is None:
        danh_sach = []          # Tạo list MỚI mỗi lần gọi
    danh_sach.append(muc)
    return danh_sach
 
print(them_muc("A"))      # ['A']
print(them_muc("B"))      # ['B']  ✅

Quy tắc bất di bất dịch

Không bao giờ dùng list, dict, set làm giá trị mặc định của tham số. Luôn dùng None rồi khởi tạo bên trong hàm.

Giá trị mặc định an toàn: int, float, str, bool, None, tuple (đều immutable).


6. Scope — Phạm vi biến

6.1. Biến cục bộ (local)

def ham_a():
    x = 10              # Biến CỤC BỘ — chỉ tồn tại bên trong hàm
    print(x)
 
ham_a()                 # 10
print(x)                # ❌ NameError: name 'x' is not defined

6.2. Biến toàn cục (global)

y = 100                 # Biến TOÀN CỤC
 
def ham_b():
    print(y)            # ✅ Đọc được biến toàn cục
 
ham_b()                 # 100

6.3. Gán biến toàn cục bên trong hàm

z = 100
 
def ham_c():
    z = 200             # Tạo biến CỤC BỘ MỚI, không sửa biến toàn cục
    print(z)            # 200
 
ham_c()
print(z)                # 100  <- không đổi

Muốn sửa thật sự thì dùng global:

z = 100
 
def ham_d():
    global z
    z = 200
 
ham_d()
print(z)                # 200

Tránh dùng global

global làm code khó theo dõi: nhìn một hàm không biết nó thay đổi những gì bên ngoài. Đây là nguồn lỗi khó tìm trong dự án lớn.

Thay vào đó: truyền giá trị vào qua tham số, trả kết quả ra bằng return.

# ❌ Dùng global
tong = 0
def cong_them(x):
    global tong
    tong += x
 
# ✅ Truyền vào - trả ra
def cong_them(tong, x):
    return tong + x
 
tong = cong_them(tong, 10)

6.4. Quy tắc LEGB

Python tìm biến theo thứ tự: Local → Enclosing → Global → Built-in.

x = "global"
 
def ngoai():
    x = "enclosing"
    def trong():
        x = "local"
        print(x)        # local
    trong()
    print(x)            # enclosing
 
ngoai()
print(x)                # global

6.5. ⚠️ Mutable truyền vào hàm CÓ THỂ bị sửa

def them(ds):
    ds.append(99)       # Sửa list gốc!
 
a = [1, 2, 3]
them(a)
print(a)                # [1, 2, 3, 99]  <- a ĐÃ ĐỔI
def gan_lai(ds):
    ds = [99]           # Gán lại -> tạo list mới, không ảnh hưởng bên ngoài
 
a = [1, 2, 3]
gan_lai(a)
print(a)                # [1, 2, 3]  <- không đổi

Nguyên nhân là “biến là nhãn dán” đã học ở 03 - Biến và kiểu dữ liệu và 08 - List và Tuple.

Cách viết an toàn:

def them_an_toan(ds):
    """Trả về list mới, không sửa list gốc."""
    moi = ds.copy()
    moi.append(99)
    return moi
 
a = [1, 2, 3]
b = them_an_toan(a)
print(a, b)             # [1, 2, 3] [1, 2, 3, 99]

7. Docstring — Chuỗi tài liệu

def tinh_thue_vat(so_tien, thue_suat=0.1):
    """
    Tính tiền thuế VAT.
 
    Tham số:
        so_tien (float): Số tiền trước thuế (VND).
        thue_suat (float): Tỷ lệ thuế, mặc định 0.1 (10%).
 
    Trả về:
        float: Số tiền thuế VAT.
 
    Ví dụ:
        >>> tinh_thue_vat(1000000)
        100000.0
    """
    return so_tien * thue_suat

Xem docstring:

print(tinh_thue_vat.__doc__)
help(tinh_thue_vat)

Trong VS Code, khi rê chuột lên tên hàm sẽ hiện docstring — rất hữu ích khi làm việc nhóm hoặc khi bạn quay lại code sau 6 tháng.

Quy chuẩn chính thức: PEP 257 — https://peps.python.org/pep-0257/


PHẦN 2 — TUẦN 17

8. *args — Số lượng đối số tùy ý

def tinh_tong(*so):
    """Tính tổng các số truyền vào (số lượng tùy ý)."""
    print(type(so))         # <class 'tuple'>
    return sum(so)
 
print(tinh_tong(1, 2))              # 3
print(tinh_tong(1, 2, 3, 4, 5))     # 15
print(tinh_tong())                  # 0

*so gom mọi đối số positional thành một tuple. Tên args chỉ là quy ước, dùng tên khác cũng được.

def in_hoa_don(ma_hd, *mat_hang):
    print(f"Hoa don: {ma_hd}")
    for i, mh in enumerate(mat_hang, 1):
        print(f"  {i}. {mh}")
 
in_hoa_don("HD001", "Ban", "Ghe", "Tu")

9. **kwargs — Đối số từ khóa tùy ý

def tao_khach_hang(**thong_tin):
    """Tạo khách hàng với các thông tin tùy ý."""
    print(type(thong_tin))          # <class 'dict'>
    for key, value in thong_tin.items():
        print(f"  {key:<12}: {value}")
 
tao_khach_hang(ma="KH001", ten="Nguyen Van A", tinh="Hue")
  ma          : KH001
  ten         : Nguyen Van A
  tinh        : Hue

9.1. Kết hợp đầy đủ

Thứ tự bắt buộc: positional → *args → default → **kwargs

def ham_day_du(a, b, *args, c=10, **kwargs):
    print(f"a = {a}")
    print(f"b = {b}")
    print(f"args = {args}")
    print(f"c = {c}")
    print(f"kwargs = {kwargs}")
 
ham_day_du(1, 2, 3, 4, c=99, x="X", y="Y")
a = 1
b = 2
args = (3, 4)
c = 99
kwargs = {'x': 'X', 'y': 'Y'}

9.2. Giải nén khi GỌI hàm

def cong(a, b, c):
    return a + b + c
 
ds = [1, 2, 3]
print(cong(*ds))            # 6  — giải nén list thành 3 đối số
 
d = {"a": 1, "b": 2, "c": 3}
print(cong(**d))            # 6  — giải nén dict thành keyword argument

10. lambda — Hàm ẩn danh

# Hàm thường
def binh_phuong(x):
    return x ** 2
 
# Lambda tương đương
binh_phuong = lambda x: x ** 2
 
print(binh_phuong(5))       # 25

Cú pháp: lambda <tham số>: <biểu thức> — chỉ một biểu thức, tự động return.

10.1. Dùng chủ yếu làm đối số cho hàm khác

hang_hoa = [
    {"ten": "Ban", "gia": 2_500_000},
    {"ten": "Ghe", "gia": 850_000},
    {"ten": "Tu",  "gia": 4_200_000},
]
 
# sorted với key
theo_gia = sorted(hang_hoa, key=lambda h: h["gia"], reverse=True)
 
# max / min với key
dat_nhat = max(hang_hoa, key=lambda h: h["gia"])
print(dat_nhat["ten"])      # Tu
 
# filter — lọc
gia_cao = list(filter(lambda h: h["gia"] >= 1_000_000, hang_hoa))
 
# map — biến đổi
ten = list(map(lambda h: h["ten"], hang_hoa))
print(ten)                  # ['Ban', 'Ghe', 'Tu']

filter/map vs list comprehension

# Dùng filter/map
list(filter(lambda h: h["gia"] >= 1_000_000, hang_hoa))
list(map(lambda h: h["ten"], hang_hoa))
 
# Dùng list comprehension — Python-ic hơn, dễ đọc hơn
[h for h in hang_hoa if h["gia"] >= 1_000_000]
[h["ten"] for h in hang_hoa]

Cộng đồng Python ưu tiên list comprehension. lambda chủ yếu dùng cho tham số key=.

10.2. Không lạm dụng

# ❌ PEP 8 khuyến cáo KHÔNG gán lambda cho biến
f = lambda x: x * 2
 
# ✅ Dùng def
def f(x):
    return x * 2

11. Type hints trong hàm

def tinh_thanh_tien(so_luong: int, don_gia: float) -> float:
    """Tính thành tiền."""
    return so_luong * don_gia
 
def tim_khach_hang(ma: str) -> dict | None:
    """Tìm khách hàng theo mã, trả về None nếu không có."""
    ...
Ký hiệuÝ nghĩa
x: intTham số x nên là số nguyên
-> floatHàm trả về số thực
dict | NoneTrả về dict hoặc None

Python KHÔNG ép buộc type hints

def cong(a: int, b: int) -> int:
    return a + b
 
print(cong("x", "y"))       # "xy" — vẫn chạy, không lỗi!

Type hints chỉ để con người và công cụ đọc. VS Code sẽ cảnh báo, nhưng Python vẫn chạy. Muốn kiểm tra thật sự thì dùng công cụ như mypy — chủ đề nâng cao.

Với người mới: cứ tập thêm type hints vào hàm, nó giúp VS Code gợi ý code tốt hơn nhiều.


12. Chương trình mẫu tổng hợp

"""
Hệ thống tính lương nhân viên.
 
Tổng hợp kiến thức M02–M10: hàm, tham số mặc định, docstring,
guard clause, list/dict, f-string.
"""
 
from decimal import Decimal
 
# ==================== HẰNG SỐ ====================
NGAY_CONG_CHUAN = 26
TY_LE_BHXH = Decimal("0.08")
TY_LE_BHYT = Decimal("0.015")
TY_LE_BHTN = Decimal("0.01")
GIAM_TRU_BAN_THAN = Decimal("11000000")
GIAM_TRU_NPT = Decimal("4400000")
 
# Biểu thuế lũy tiến: (giới hạn trên, thuế suất, số trừ)
BIEU_THUE = [
    (Decimal("5000000"),  Decimal("0.05"), Decimal("0")),
    (Decimal("10000000"), Decimal("0.10"), Decimal("250000")),
    (Decimal("18000000"), Decimal("0.15"), Decimal("750000")),
    (Decimal("32000000"), Decimal("0.20"), Decimal("1650000")),
    (Decimal("52000000"), Decimal("0.25"), Decimal("3250000")),
    (Decimal("80000000"), Decimal("0.30"), Decimal("5850000")),
    (None,                Decimal("0.35"), Decimal("9850000")),
]
 
 
# ==================== CÁC HÀM TÍNH TOÁN ====================
 
def tinh_luong_theo_cong(luong_co_ban, ngay_cong, ngay_chuan=NGAY_CONG_CHUAN):
    """
    Tính lương theo số ngày công thực tế.
 
    Tham số:
        luong_co_ban (Decimal): Lương cơ bản theo hợp đồng.
        ngay_cong (Decimal): Số ngày công thực tế.
        ngay_chuan (int): Số ngày công chuẩn của tháng, mặc định 26.
 
    Trả về:
        Decimal: Lương theo ngày công.
    """
    if ngay_chuan <= 0:
        return Decimal("0")
    return luong_co_ban / ngay_chuan * ngay_cong
 
 
def tinh_bao_hiem(luong_dong_bh):
    """
    Tính các khoản bảo hiểm bắt buộc.
 
    Trả về:
        dict: {'bhxh', 'bhyt', 'bhtn', 'tong'}
    """
    bhxh = luong_dong_bh * TY_LE_BHXH
    bhyt = luong_dong_bh * TY_LE_BHYT
    bhtn = luong_dong_bh * TY_LE_BHTN
    return {
        "bhxh": bhxh,
        "bhyt": bhyt,
        "bhtn": bhtn,
        "tong": bhxh + bhyt + bhtn,
    }
 
 
def tinh_giam_tru(so_nguoi_phu_thuoc=0, bao_hiem=Decimal("0")):
    """Tính tổng các khoản giảm trừ khi tính thuế TNCN."""
    return (GIAM_TRU_BAN_THAN
            + GIAM_TRU_NPT * so_nguoi_phu_thuoc
            + bao_hiem)
 
 
def tinh_thue_tncn(thu_nhap_tinh_thue):
    """
    Tính thuế TNCN theo biểu thuế lũy tiến từng phần.
 
    Trả về:
        tuple: (số tiền thuế, tên bậc thuế)
    """
    if thu_nhap_tinh_thue <= 0:
        return Decimal("0"), "Khong phai nop thue"
 
    for bac, (gioi_han, ty_le, so_tru) in enumerate(BIEU_THUE, start=1):
        if gioi_han is None or thu_nhap_tinh_thue <= gioi_han:
            thue = thu_nhap_tinh_thue * ty_le - so_tru
            return thue, f"Bac {bac} ({ty_le:.0%})"
 
    return Decimal("0"), "Khong xac dinh"
 
 
def tinh_bang_luong(ho_ten, luong_co_ban, ngay_cong, phu_cap=Decimal("0"),
                    so_nguoi_phu_thuoc=0):
    """
    Tính toàn bộ bảng lương cho một nhân viên.
 
    Trả về:
        dict: Toàn bộ chi tiết bảng lương.
    """
    luong_cong = tinh_luong_theo_cong(luong_co_ban, ngay_cong)
    tong_thu_nhap = luong_cong + phu_cap
 
    bh = tinh_bao_hiem(luong_co_ban)
    giam_tru = tinh_giam_tru(so_nguoi_phu_thuoc, bh["tong"])
 
    thu_nhap_tinh_thue = max(tong_thu_nhap - giam_tru, Decimal("0"))
    thue, bac_thue = tinh_thue_tncn(thu_nhap_tinh_thue)
 
    return {
        "ho_ten": ho_ten,
        "luong_co_ban": luong_co_ban,
        "ngay_cong": ngay_cong,
        "luong_cong": luong_cong,
        "phu_cap": phu_cap,
        "tong_thu_nhap": tong_thu_nhap,
        "bao_hiem": bh,
        "giam_tru": giam_tru,
        "thu_nhap_tinh_thue": thu_nhap_tinh_thue,
        "thue": thue,
        "bac_thue": bac_thue,
        "thuc_linh": tong_thu_nhap - bh["tong"] - thue,
    }
 
 
# ==================== CÁC HÀM HIỂN THỊ ====================
 
def in_dong(nhan, gia_tri, do_rong=55):
    """In một dòng nhãn : giá trị được căn lề."""
    print(f"{nhan:<32}: {gia_tri:>{do_rong - 34},.0f}")
 
 
def in_bang_luong(bl):
    """In bảng lương chi tiết của một nhân viên."""
    print("=" * 55)
    print(f"BANG LUONG: {bl['ho_ten']}".center(55))
    print("=" * 55)
    in_dong("Luong co ban", bl["luong_co_ban"])
    print(f"{'Ngay cong':<32}: {bl['ngay_cong']:>21}")
    in_dong("Luong theo cong", bl["luong_cong"])
    in_dong("Phu cap", bl["phu_cap"])
    print("-" * 55)
    in_dong("TONG THU NHAP", bl["tong_thu_nhap"])
    print("-" * 55)
    in_dong("BHXH (8%)", bl["bao_hiem"]["bhxh"])
    in_dong("BHYT (1.5%)", bl["bao_hiem"]["bhyt"])
    in_dong("BHTN (1%)", bl["bao_hiem"]["bhtn"])
    in_dong("Tong bao hiem", bl["bao_hiem"]["tong"])
    print("-" * 55)
    in_dong("Tong giam tru", bl["giam_tru"])
    in_dong("Thu nhap tinh thue", bl["thu_nhap_tinh_thue"])
    print(f"{'Bac thue':<32}: {bl['bac_thue']:>21}")
    in_dong("Thue TNCN", bl["thue"])
    print("=" * 55)
    in_dong("THUC LINH", bl["thuc_linh"])
    print("=" * 55)
 
 
def in_bang_tong_hop(danh_sach_bl):
    """In bảng tổng hợp lương toàn công ty."""
    print()
    print("=" * 80)
    print("BANG TONG HOP LUONG".center(80))
    print("=" * 80)
    print(f"{'STT':<5}{'HO TEN':<24}{'TONG TN':>16}{'BAO HIEM':>13}{'THUE':>10}{'THUC LINH':>14}")
    print("-" * 80)
 
    for i, bl in enumerate(danh_sach_bl, start=1):
        print(f"{i:<5}{bl['ho_ten']:<24}"
              f"{bl['tong_thu_nhap']:>16,.0f}"
              f"{bl['bao_hiem']['tong']:>13,.0f}"
              f"{bl['thue']:>10,.0f}"
              f"{bl['thuc_linh']:>14,.0f}")
 
    print("-" * 80)
    print(f"{'TONG CONG':<29}"
          f"{sum(b['tong_thu_nhap'] for b in danh_sach_bl):>16,.0f}"
          f"{sum(b['bao_hiem']['tong'] for b in danh_sach_bl):>13,.0f}"
          f"{sum(b['thue'] for b in danh_sach_bl):>10,.0f}"
          f"{sum(b['thuc_linh'] for b in danh_sach_bl):>14,.0f}")
    print("=" * 80)
 
 
# ==================== CHƯƠNG TRÌNH CHÍNH ====================
 
nhan_vien = [
    {"ho_ten": "Nguyen Van A", "luong": "35000000", "cong": "26", "pc": "3000000", "npt": 2},
    {"ho_ten": "Tran Thi B",   "luong": "18000000", "cong": "24", "pc": "1500000", "npt": 0},
    {"ho_ten": "Le Van C",     "luong": "12000000", "cong": "26", "pc": "800000",  "npt": 1},
    {"ho_ten": "Pham Thi D",   "luong": "55000000", "cong": "26", "pc": "5000000", "npt": 3},
]
 
bang_luong = [
    tinh_bang_luong(
        ho_ten=nv["ho_ten"],
        luong_co_ban=Decimal(nv["luong"]),
        ngay_cong=Decimal(nv["cong"]),
        phu_cap=Decimal(nv["pc"]),
        so_nguoi_phu_thuoc=nv["npt"],
    )
    for nv in nhan_vien
]
 
in_bang_luong(bang_luong[0])
in_bang_tong_hop(bang_luong)

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

Bài 1 — Hàm cơ bản

Viết các hàm sau, mỗi hàm có docstring đầy đủ:

  1. la_so_chan(n) → trả về True/False
  2. tinh_giai_thua(n) → trả về n!
  3. dem_nguyen_am(chuoi) → trả về số nguyên âm
  4. dao_nguoc(chuoi) → trả về chuỗi đảo ngược
  5. la_so_nguyen_to(n) → trả về True/False

Bài 2 — Tham số mặc định

Viết tinh_chiet_khau(so_tien, ty_le=0.05, so_tien_toi_thieu=1_000_000):

  • Nếu so_tien < so_tien_toi_thieu → trả về 0
  • Ngược lại → trả về so_tien * ty_le

Gọi hàm với 3 cách khác nhau (positional, keyword, kết hợp).

Bài 3 — Trả về nhiều giá trị

Viết phan_tich_chuoi(s) trả về dict chứa: độ dài, số chữ cái, số chữ số, số khoảng trắng, chuỗi viết hoa, chuỗi đảo ngược.

Bài 4 — Bẫy mutable default

Chạy đoạn code sau, giải thích kết quả, rồi sửa lại cho đúng:

def them_hoa_don(ma, danh_sach=[]):
    danh_sach.append(ma)
    return danh_sach
 
print(them_hoa_don("HD001"))
print(them_hoa_don("HD002"))
print(them_hoa_don("HD003"))

Bài 5 — Scope

Dự đoán kết quả trước khi chạy:

x = 10
 
def f1():
    print(x)
 
def f2():
    x = 20
    print(x)
 
def f3():
    global x
    x = 30
 
def f4(ds):
    ds.append(99)
 
def f5(ds):
    ds = [1]
 
f1()
f2()
print(x)
f3()
print(x)
 
a = [1, 2]
f4(a)
print(a)
f5(a)
print(a)

Bài 6 — *args và **kwargs

  1. tinh_trung_binh(*so) — tính trung bình các số truyền vào, xử lý trường hợp không có số nào
  2. tao_chuoi_ket_noi(**thong_tin) — nhận server, database, user… trả về chuỗi dạng server=X;database=Y;...

Bài 7 — Refactor

Lấy chương trình quan_ly_ban_hang.py bạn viết ở 09 - Dict và Set bài 8, tách toàn bộ thành hàm:

  • Mỗi chức năng của menu là một hàm
  • Tách riêng hàm tính toán và hàm hiển thị
  • Mỗi hàm có docstring
  • Hàm main() chứa vòng lặp menu

Bài 8 — Thư viện hàm cá nhân

Tạo file tien_ich.py chứa các hàm bạn hay dùng:

  • dinh_dang_tien(so, don_vi="VND") → "1.234.567 VND"
  • chuan_hoa_ten(ten) → chuẩn hóa họ tên
  • lam_tron_len(so, chu_so=0) → làm tròn kiểu 0.5 lên
  • che_chuoi(s, so_ky_tu_hien=4) → "********3456"
  • phan_tram(phan, tong) → tính tỷ lệ %, xử lý chia cho 0

File này bạn sẽ import vào các chương trình khác ở 12 - Module, Package và Thư viện chuẩn.


14. Tự kiểm tra

  • Tôi viết được hàm có tham số và giá trị trả về
  • Tôi phân biệt print và return trong hàm
  • Tôi biết dùng keyword argument cho hàm nhiều tham số
  • Tôi biết bẫy mutable default argument và cách tránh
  • Tôi hiểu scope: biến cục bộ, toàn cục, quy tắc LEGB
  • Tôi biết vì sao nên tránh global
  • Tôi viết được docstring
  • Tôi biết *args, **kwargs và cách giải nén khi gọi hàm
  • Tôi biết lambda và khi nào nên dùng
  • Tôi viết được guard clause thay cho if lồng nhau

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

NguồnNội dungLink
Tutorial — Defining FunctionsNhập môn hàmhttps://docs.python.org/3/tutorial/controlflow.html#defining-functions
More on Defining FunctionsTham số mặc định, *args, **kwargs, lambdahttps://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions
PEP 257 — Docstring ConventionsQuy chuẩn docstringhttps://peps.python.org/pep-0257/
PEP 484 — Type HintsGợi ý kiểuhttps://peps.python.org/pep-0484/
typing moduleThư viện type hintshttps://docs.python.org/3/library/typing.html
Execution model — Naming and bindingQuy tắc LEGBhttps://docs.python.org/3/reference/executionmodel.html#naming-and-binding

Tiếp theo → 11 - Xử lý lỗi (Exception)