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 ích
Giả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àmchao_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 defineddef 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àmdef 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_giaprint(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 + bcong(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ƯỚCtao_hoa_don("KH001", so_tien=5_000_000, ngay="08/08/2026")# ❌ Sai — positional sau keywordtao_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:
def cong_in(a, b): print(a + b) # CHỈ in ra màn hìnhdef cong_tra_ve(a, b): return a + b # TRẢ VỀ giá trị để dùng tiếpx = cong_in(1, 2) # In ra: 3print(x) # None <- không trả về gìy = cong_tra_ve(1, 2) # Không in gìprint(y) # 3print(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êngdef tinh_thanh_tien(sl, gia): """Tính toán — trả về số.""" return sl * giadef 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âudef 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ễ đọcdef 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"
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:
# ✅ ĐÚNGdef 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_sachprint(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() # 10print(x) # ❌ NameError: name 'x' is not defined
6.2. Biến toàn cục (global)
y = 100 # Biến TOÀN CỤCdef ham_b(): print(y) # ✅ Đọc được biến toàn cụcham_b() # 100
6.3. Gán biến toàn cục bên trong hàm
z = 100def 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) # 200ham_c()print(z) # 100 <- không đổi
Muốn sửa thật sự thì dùng global:
z = 100def ham_d(): global z z = 200ham_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 globaltong = 0def cong_them(x): global tong tong += x# ✅ Truyền vào - trả radef cong_them(tong, x): return tong + xtong = 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) # enclosingngoai()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àia = [1, 2, 3]gan_lai(a)print(a) # [1, 2, 3] <- không đổi
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)) # 3print(tinh_tong(1, 2, 3, 4, 5)) # 15print(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
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 + cds = [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ườngdef binh_phuong(x): return x ** 2# Lambda tương đươngbinh_phuong = lambda x: x ** 2print(binh_phuong(5)) # 25
Cú pháp: lambda <tham số>: <biểu thức> — chỉ một biểu thức, tự động return.
# Dùng filter/maplist(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ếnf = lambda x: x * 2# ✅ Dùng defdef 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_giadef 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: int
Tham số x nên là số nguyên
-> float
Hàm trả về số thực
dict | None
Trả về dict hoặc None
Python KHÔNG ép buộc type hints
def cong(a: int, b: int) -> int: return a + bprint(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 = 26TY_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_congdef 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)
x = 10def f1(): print(x)def f2(): x = 20 print(x)def f3(): global x x = 30def 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
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
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: