Hàm (Function)
Giai đoạn 2 – Cấu Trúc Dữ Liệu & Hàm · Mục tiêu: Viết hàm tái sử dụng, hiểu tham số, return, scope, lambda và đệ quy.
1. Định nghĩa hàm
def chao(ten):
print(f"Xin chào, {ten}! ")
chao("Vũ") # Xin chào, Vũ!
chao("An") # Xin chào, An!
2. Tham số
Nhiều tham số & Giá trị mặc định
def tinh_dien_tich(dai, rong):
return dai * rong
tinh_dien_tich(5, 3) # Positional
tinh_dien_tich(rong=3, dai=5) # Keyword
def chao(ten, loi_chao="Xin chào"):
print(f"{loi_chao}, {ten}!")
chao("Vũ") # Xin chào, Vũ!
chao("Vũ", "Hello") # Hello, Vũ!
:::warning Tham số mặc định phải ở cuối
def ham(a, b, c=10): #
pass
def ham(a, b=10, c): # SyntaxError
pass
:::
3. Return
def cong(a, b):
return a + b
kq = cong(3, 5) # 8
# Return nhiều giá trị
def thong_ke(ds):
return min(ds), max(ds), sum(ds) / len(ds)
nho, lon, tb = thong_ke([5, 8, 3, 9, 1])
:::info Return vs Print
print() chỉ in ra màn hình — không lưu được. return trả về giá trị để dùng tiếp.
:::
4. *args và **kwargs
# *args — nhận tuple tham số tùy ý
def tong(*args):
return sum(args)
tong(1, 2, 3) # 6
tong(10, 20, 30, 40) # 100
# **kwargs — nhận dict tham số tùy ý
def thong_tin(**kwargs):
for k, v in kwargs.items():
print(f" {k}: {v}")
thong_tin(ho_ten="Vũ", tuoi=20, lop="12A1")
5. Phạm vi biến (Scope)
x = 10 # global
def ham():
x = 20 # local — khác biến global
print(f"Trong hàm: {x}")
ham() # 20
print(f"Ngoài hàm: {x}") # 10
:::tip Hạn chế dùng global
Thay vì global, hãy truyền tham số và return giá trị.
:::
6. Lambda
Hàm ẩn danh, viết trên 1 dòng:
binh_phuong = lambda x: x ** 2
cong = lambda a, b: a + b
# Kết hợp sorted, map, filter
hs = [{"ten": "An", "diem": 8.5}, {"ten": "Bình", "diem": 7.2}]
sorted(hs, key=lambda x: x["diem"], reverse=True)
list(map(lambda x: x**2, [1, 2, 3])) # [1, 4, 9]
list(filter(lambda x: x % 2 == 0, [1,2,3,4])) # [2, 4]
7. Đệ quy (Recursion)
Hàm gọi lại chính nó:
def giai_thua(n):
if n <= 1:
return 1
return n * giai_thua(n - 1)
giai_thua(5) # 120
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
:::warning Luôn có điều kiện dừng Python giới hạn 1000 lần gọi đệ quy. :::
8. Type Hints
Gợi ý kiểu (không bắt buộc, giúp code rõ ràng hơn):
def tinh_bmi(can_nang: float, chieu_cao: float) -> float:
return can_nang / (chieu_cao ** 2)
def xep_loai(diem: float) -> str:
if diem >= 8: return "Giỏi"
if diem >= 6.5: return "Khá"
if diem >= 5: return "TB"
return "Yếu"
Bài tập thực hành
Trắc nghiệm
-
Hàm không có
returntrả về gì?- A.
0 - B.
"" - C.
None - D. Lỗi
- A.
-
lambda x, y: x * ytương đương với hàm nào?- A.
def f(x, y): print(x * y) - B.
def f(x, y): return x * y - C.
def f(x, y): x * y - D.
def f(): return x * y
- A.
-
Biến khai báo bên trong hàm có thể dùng ở ngoài hàm không?
- A. Có, luôn luôn
- B. Có, nếu dùng
global - C. Không, biến local chỉ tồn tại trong hàm
- D. Có, nhưng chỉ với biến số
-
*argstrong định nghĩa hàm nhận tham số dưới dạng nào?- A. List
- B. Set
- C. Dict
- D. Tuple
Bài 1: Tính lương nhân viên
Đề bài: Viết hàm tinh_luong(gio_lam, luong_gio, thuong=0) tính tổng lương. Nếu làm hơn 8 tiếng/ngày, phần dư được tính 1.5 lần.
tinh_luong(8, 50000) → 400000.0
tinh_luong(10, 50000) → 550000.0 (8h × 50k + 2h × 75k)
tinh_luong(10, 50000, 100000) → 650000.0
Gợi ý
- Nếu
gio_lam <= 8: lương =gio_lam * luong_gio - Nếu
gio_lam > 8: lương =8 * luong_gio + (gio_lam - 8) * luong_gio * 1.5 - Cộng thêm
thuongvào kết quả cuối. - Tham số
thuong=0là giá trị mặc định — không cần truyền nếu không có thưởng.
Lời giải
def tinh_luong(gio_lam, luong_gio, thuong=0):
if gio_lam <= 8:
luong_chinh = gio_lam * luong_gio
else:
luong_chinh = 8 * luong_gio + (gio_lam - 8) * luong_gio * 1.5
return luong_chinh + thuong
# Kiểm tra
print(tinh_luong(8, 50_000)) # 400000.0
print(tinh_luong(10, 50_000)) # 550000.0
print(tinh_luong(10, 50_000, 100_000)) # 650000.0
Kết quả:
400000.0
550000.0
650000.0
Bài 2: Kiểm tra số nguyên tố
Đề bài: Viết hàm la_nguyen_to(n) trả về True nếu n là số nguyên tố. Dùng hàm này để in tất cả số nguyên tố từ 1 đến 50.
la_nguyen_to(7) → True
la_nguyen_to(10) → False
Nguyên tố 1–50: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
Gợi ý
- Số nguyên tố lớn hơn 1 và chỉ chia hết cho 1 và chính nó.
- Tối ưu: chỉ kiểm tra ước từ
2đếnint(n**0.5) + 1— nếuncó ước lớn hơn√nthì ước còn lại phải nhỏ hơn√n. - Tìm thấy ước → không phải nguyên tố,
return Falsengay.
Lời giải
def la_nguyen_to(n):
if n < 2:
return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
# In các số nguyên tố từ 1 đến 50
print("Nguyên tố 1–50:", end=" ")
for so in range(1, 51):
if la_nguyen_to(so):
print(so, end=" ")
print()
# Cách gọn dùng list comprehension
nguyen_to = [x for x in range(1, 51) if la_nguyen_to(x)]
print(f"Dạng list: {nguyen_to}")
Kết quả:
Nguyên tố 1–50: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
Dạng list: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
Bài 3: Đếm nguyên âm trong chuỗi
Đề bài: Viết hàm dem_nguyen_am(chuoi) đếm số lượng nguyên âm (a, e, i, o, u và cả có dấu tiếng Việt) trong chuỗi, không phân biệt hoa thường.
dem_nguyen_am("Hello World") → 3
dem_nguyen_am("Học Python") → 3
dem_nguyen_am("Python rất thú vị!") → 4
Gợi ý
- Định nghĩa tập hợp
set()chứa tất cả nguyên âm (kể cả có dấu tiếng Việt). - Chuyển chuỗi về chữ thường bằng
.lower()trước khi kiểm tra. - Dùng
sum(1 for ky_tu in chuoi if ky_tu in nguyen_am)— gọn và hiệu quả.
Lời giải
def dem_nguyen_am(chuoi):
nguyen_am = set(
"aeiou"
"àáảãạăắặẵẳằâấậẫẩ"
"èéẹẻẽêếệểễề"
"ìíịỉĩ"
"òóọỏõôốộổỗồơớợởỡờ"
"ùúụủũưứựửữừ"
"ỳýỵỷỹ"
)
chuoi_thuong = chuoi.lower()
return sum(1 for ky_tu in chuoi_thuong if ky_tu in nguyen_am)
# Kiểm tra
cac_chuoi = [
"Hello World",
"Học Python",
"Python rất thú vị!",
"xyz"
]
for s in cac_chuoi:
print(f"'{s}' → {dem_nguyen_am(s)} nguyên âm")
Kết quả:
'Hello World' → 3 nguyên âm
'Học Python' → 3 nguyên âm
'Python rất thú vị!' → 4 nguyên âm
'xyz' → 0 nguyên âm
Bài 4: Đảo ngược chuỗi bằng đệ quy
Đề bài: Viết hàm dao_chuoi(s) đảo ngược chuỗi bằng đệ quy (không dùng slicing [::-1] hay reversed()).
dao_chuoi("Python") → "nohtyP"
dao_chuoi("hello") → "olleh"
dao_chuoi("") → ""
Gợi ý
- Điều kiện dừng: chuỗi rỗng hoặc có 1 ký tự → trả về chính nó.
- Bước đệ quy:
dao_chuoi(s[1:]) + s[0]- Đảo phần còn lại (bỏ ký tự đầu), rồi ghép ký tự đầu vào CUỐI.
- Mỗi lần gọi đệ quy, chuỗi ngắn đi 1 ký tự cho đến khi rỗng.
Lời giải
def dao_chuoi(s):
# Điều kiện dừng
if len(s) <= 1:
return s
# Đệ quy: ghép ký tự đầu vào SAU phần còn lại đã đảo
return dao_chuoi(s[1:]) + s[0]
# Kiểm tra
for s in ["Python", "hello", "racecar", "", "a"]:
print(f"dao_chuoi('{s}') → '{dao_chuoi(s)}'")
Kết quả:
dao_chuoi('Python') → 'nohtyP'
dao_chuoi('hello') → 'olleh'
dao_chuoi('racecar') → 'racecar'
dao_chuoi('') → ''
dao_chuoi('a') → 'a'
:::tip So sánh các cách đảo chuỗi
# Đệ quy (bài này — luyện tư duy)
dao_chuoi("Python")
# Slicing — ngắn nhất, thực tế dùng cách này
"Python"[::-1]
# Hàm có sẵn
"".join(reversed("Python"))
:::
Bài 5: Thống kê điểm lớp học
Đề bài: Viết hàm thong_ke_diem(danh_sach_diem) nhận vào một list điểm, trả về tuple gồm: (cao_nhat, thap_nhat, trung_binh, so_hoc_sinh_gioi). Học sinh giỏi là người có điểm ≥ 8.0.
diem = [7.5, 8.2, 6.0, 9.5, 5.5, 8.0, 7.0, 9.0, 4.5, 8.8]
cao, thap, tb, gioi = thong_ke_diem(diem)
# → cao=9.5, thap=4.5, tb=7.4, gioi=4
Gợi ý
- Dùng hàm có sẵn:
max(),min(),sum(),len(). - Đếm học sinh giỏi:
sum(1 for d in ds if d >= 8.0). returnnhiều giá trị: Python tự đóng gói thành tuple —return a, b, c, d.
Lời giải
def thong_ke_diem(danh_sach_diem):
cao_nhat = max(danh_sach_diem)
thap_nhat = min(danh_sach_diem)
trung_binh = round(sum(danh_sach_diem) / len(danh_sach_diem), 2)
so_gioi = sum(1 for d in danh_sach_diem if d >= 8.0)
return cao_nhat, thap_nhat, trung_binh, so_gioi
# Kiểm tra
diem = [7.5, 8.2, 6.0, 9.5, 5.5, 8.0, 7.0, 9.0, 4.5, 8.8]
cao, thap, tb, gioi = thong_ke_diem(diem)
print(f"Điểm cao nhất : {cao}")
print(f"Điểm thấp nhất : {thap}")
print(f"Điểm trung bình : {tb}")
print(f"Số học sinh giỏi: {gioi}")
Kết quả:
Điểm cao nhất : 9.5
Điểm thấp nhất : 4.5
Điểm trung bình : 7.4
Số học sinh giỏi: 4
Bài 6: Lambda & Sắp xếp danh sách
Đề bài: Cho danh sách học sinh sau. Dùng lambda để:
- Sắp xếp theo điểm giảm dần.
- Lọc học sinh có điểm ≥ 7.0.
- Tạo list chuỗi
"Tên: điểm"bằngmap().
hoc_sinh = [
{"ten": "An", "diem": 8.5},
{"ten": "Bình", "diem": 6.0},
{"ten": "Chi", "diem": 9.2},
{"ten": "Dũng", "diem": 7.0},
{"ten": "Em", "diem": 5.5},
]
Gợi ý
sorted(ds, key=lambda x: x["diem"], reverse=True)— sắp xếp giảm dần.filter(lambda x: x["diem"] >= 7.0, ds)— lọc theo điều kiện.map(lambda x: f"{x['ten']}: {x['diem']}", ds)— biến đổi từng phần tử.- Bọc
filter()vàmap()tronglist()để ra danh sách.
Lời giải
hoc_sinh = [
{"ten": "An", "diem": 8.5},
{"ten": "Bình", "diem": 6.0},
{"ten": "Chi", "diem": 9.2},
{"ten": "Dũng", "diem": 7.0},
{"ten": "Em", "diem": 5.5},
]
# 1. Sắp xếp giảm dần theo điểm
theo_diem = sorted(hoc_sinh, key=lambda x: x["diem"], reverse=True)
print("Xếp hạng:")
for i, hs in enumerate(theo_diem, 1):
print(f" {i}. {hs['ten']} — {hs['diem']}")
# 2. Lọc học sinh điểm >= 7.0
du_chuan = list(filter(lambda x: x["diem"] >= 7.0, hoc_sinh))
print("\nHọc sinh đạt (≥ 7.0):", [hs["ten"] for hs in du_chuan])
# 3. Tạo chuỗi "Tên: điểm"
chuoi_hs = list(map(lambda x: f"{x['ten']}: {x['diem']}", hoc_sinh))
print("\nDanh sách:", chuoi_hs)
Kết quả:
Xếp hạng:
1. Chi — 9.2
2. An — 8.5
3. Dũng — 7.0
4. Bình — 6.0
5. Em — 5.5
Học sinh đạt (≥ 7.0): ['An', 'Chi', 'Dũng']
Danh sách: ['An: 8.5', 'Bình: 6.0', 'Chi: 9.2', 'Dũng: 7.0', 'Em: 5.5']
Bài 7: Máy tính bỏ túi với **kwargs
Đề bài: Viết hàm may_tinh(**phep_tinh) nhận các phép tính dưới dạng keyword arguments và in kết quả. Hỗ trợ: cong, tru, nhan, chia, luy_thua.
may_tinh(cong=(10, 5), tru=(10, 3), nhan=(4, 7))
cong(10, 5) = 15
tru(10, 3) = 7
nhan(4, 7) = 28
Gợi ý
**kwargsnhận vào dạng dict: key là tên phép tính, value là tuple(a, b).- Dùng dict ánh xạ tên → lambda để tra cứu và gọi hàm tính.
- Xử lý chia cho 0: kiểm tra
b != 0trước khi chia. - Dùng
f-stringđể in kết quả căn lề đẹp.
Lời giải
def may_tinh(**phep_tinh):
phep_map = {
"cong" : lambda a, b: a + b,
"tru" : lambda a, b: a - b,
"nhan" : lambda a, b: a * b,
"chia" : lambda a, b: a / b if b != 0 else "Lỗi: chia cho 0",
"luy_thua" : lambda a, b: a ** b,
}
for ten, cap_so in phep_tinh.items():
a, b = cap_so
if ten in phep_map:
kq = phep_map[ten](a, b)
print(f"{ten}({a}, {b}){'':<8} = {kq}")
else:
print(f"Phép tính '{ten}' không được hỗ trợ.")
# Kiểm tra
may_tinh(cong=(10, 5), tru=(10, 3), nhan=(4, 7))
print("---")
may_tinh(chia=(20, 4), luy_thua=(2, 10))
print("---")
may_tinh(chia=(9, 0)) # Thử chia cho 0
Kết quả:
cong(10, 5) = 15
tru(10, 3) = 7
nhan(4, 7) = 28
---
chia(20, 4) = 5.0
luy_thua(2, 10) = 1024
---
chia(9, 0) = Lỗi: chia cho 0
Bài 8: Giai thừa & Fibonacci bằng đệ quy
Đề bài: Viết hai hàm đệ quy:
giai_thua(n)— tínhn!fibonacci(n)— trả về số Fibonacci thứn
In bảng so sánh kết quả từ n = 0 đến n = 10.
Gợi ý
- Giai thừa: Điều kiện dừng
n <= 1→ return1. Đệ quy:n * giai_thua(n - 1). - Fibonacci: Điều kiện dừng
n <= 1→ returnn. Đệ quy:fibonacci(n-1) + fibonacci(n-2). - Căn lề khi in bảng:
{x:>10}— canh phải 10 ký tự.
Lời giải
def giai_thua(n):
if n <= 1:
return 1
return n * giai_thua(n - 1)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# In bảng so sánh
print(f"{'n':>3} | {'n!':>10} | {'Fibonacci':>10}")
print("-" * 30)
for i in range(11):
print(f"{i:>3} | {giai_thua(i):>10} | {fibonacci(i):>10}")
Kết quả:
n | n! | Fibonacci
------------------------------
0 | 1 | 0
1 | 1 | 1
2 | 2 | 1
3 | 6 | 2
4 | 24 | 3
5 | 120 | 5
6 | 720 | 8
7 | 5040 | 13
8 | 40320 | 21
9 | 362880 | 34
10 | 3628800 | 55
:::warning Fibonacci đệ quy chậm
fibonacci(n) tính lại các giá trị nhiều lần. Dùng memoization để tối ưu:
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci_nhanh(n):
if n <= 1:
return n
return fibonacci_nhanh(n-1) + fibonacci_nhanh(n-2)
:::
Quay lại: Roadmap · Bài tiếp: Bài 10 – Xử Lý File & Ngoại Lệ