Trong bài này
Lan cần tính tiền cho 3 quyển vở giá 8 nghìn đồng mỗi quyển. Sau đó Lan muốn cộng thêm 5 nghìn đồng tiền vận chuyển. Hàm in tiền hàng sẽ hiện con số, nhưng Lan còn cần dùng con số ấy trong phép tính tiếp theo.
return đưa kết quả về nơi gọi để dùng tiếp
Hãy tính tiền hàng từ số lượng và đơn giá. Đơn vị trong ví dụ là nghìn đồng.
def tinh_tien(so_luong, don_gia):
return so_luong * don_gia
tien_hang = tinh_tien(3, 8)
tong = tien_hang + 5
print(tien_hang)
print(tong)24 29
Giá trị trả về (return value) là kết quả hàm gửi cho nơi gọi. return tính biểu thức bên phải rồi kết thúc lần gọi hàm. Ở đây kết quả 24 được gán vào tien_hang; sau đó chương trình cộng phí để được 29.
Biểu thức gọi hàm có thể xuất hiện trực tiếp trong một phép tính. Chạy ví dụ với hai hộp bút giá 6 nghìn đồng và phí 4 nghìn đồng:
def tinh_tien(so_luong, don_gia):
return so_luong * don_gia
print(tinh_tien(2, 6) + 4)16
Python tính lời gọi trước, rồi cộng 4 vào kết quả. Nếu không lưu kết quả vào biến, bạn vẫn có thể dùng nó ngay như một số.
Nơi gọiTruyền 3 và 8HàmTính 3 nhân 8returnGửi 24 về nơi gọiNơi gọiGán kết quả rồi cộng phí
In ra và trả về là hai việc khác nhau
Hãy so sánh hai hàm cùng tính số tiền. Hàm đầu hiển thị kết quả; hàm sau gửi kết quả về nơi gọi.
def in_tien(so_luong, don_gia):
print(so_luong * don_gia)
def lay_tien(so_luong, don_gia):
return so_luong * don_gia
a = in_tien(2, 7)
b = lay_tien(2, 7)
print(a)
print(b)14 None 14
Lời gọi in_tien đã in 14, nhưng không gửi số đó làm kết quả. Biến a nhận None. Lời gọi lay_tien không tự in; biến b nhận 14, rồi lệnh cuối hiển thị giá trị này.
None là một giá trị đặc biệt biểu thị không có giá trị kết quả được cung cấp. Khi hàm kết thúc mà không gặp return, Python trả về None. Một lệnh return đứng một mình cũng trả về None.
def thong_bao():
print("Da nhan")
return
ket_qua = thong_bao()
print(ket_qua)Da nhan None
Không thể cộng None với một số. Ví dụ sau cố ý gây lỗi:
def in_tien(so_luong, don_gia):
print(so_luong * don_gia)
tong = in_tien(2, 7) + 4Lỗi khi chạy, ở dòng 4
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
Python báo TypeError vì kết quả lời gọi là None, không phải 14. Hãy dùng return trong hàm nếu nơi gọi cần tính tiếp. Bạn vẫn có thể dùng print() ở ngoài hàm để hiển thị kết quả.
return sớm kết thúc ngay lần gọi hiện tại
Một cửa hàng chỉ nhận số lượng dương. Hàm sau trả về 0 nếu số lượng không hợp lệ; nếu hợp lệ, hàm tính tiền.
def tien_hang(so_luong, don_gia):
if so_luong <= 0:
return 0
return so_luong * don_gia
print(tien_hang(0, 8))
print(tien_hang(3, 8))0 24
Với 0, Python gặp return 0 và rời hàm ngay. Dòng nhân không chạy trong lần gọi đó. Với 3, điều kiện sai nên Python đi tiếp tới return thứ hai.
Trả về sớm (early return) giúp xử lý trường hợp đặc biệt trước. Sau return, các lệnh còn lại trong cùng lần gọi không được thực hiện. Một lần gọi mới bắt đầu lại từ đầu thân hàm.
def kiem_tra(diem):
if diem >= 5:
return "Dat"
print("Can co gang")
return "Chua dat"
print(kiem_tra(7))
print(kiem_tra(4))Dat Can co gang Chua dat
Lần đầu không in lời nhắc trong hàm. Lần sau in lời nhắc rồi trả về chuỗi. Khi một nhánh thiếu return, nhánh đó có thể đi đến cuối hàm và nhận None; hãy kiểm tra từng đường đi.
Trả nhiều giá trị rồi nhận đúng số biến
Một phép chia có thể cần cả thương nguyên và số dư. Hàm có thể gửi cả hai trong cùng một câu lệnh:
def chia_keo(so_keo, so_ban):
thuong = so_keo // so_ban
du = so_keo % so_ban
return thuong, du
moi_ban, con_lai = chia_keo(17, 5)
print(moi_ban)
print(con_lai)3 2
return thuong, du đóng gói hai giá trị theo thứ tự thành một bộ giá trị (tuple). Ở nơi gọi, moi_ban, con_lai nhận lần lượt từng giá trị. Ở đây mỗi bạn nhận 3 viên, còn lại 2 viên. so_ban phải khác 0 để phép chia hợp lệ.
Nếu chỉ dùng một biến, biến đó nhận cả bộ giá trị. Bạn sẽ học chi tiết tuple ở Bài 13.
def chia_keo(so_keo, so_ban):
return so_keo // so_ban, so_keo % so_ban
ket_qua = chia_keo(11, 4)
print(ket_qua)(2, 3)
Khi gán cho nhiều biến, số biến phải khớp số giá trị trong bộ. Thứ tự cũng quan trọng: biến đầu nhận thương, biến sau nhận số dư.
def chia_keo(so_keo, so_ban):
return so_keo // so_ban, so_keo % so_ban
a, b, c = chia_keo(11, 4)Lỗi khi chạy, ở dòng 4
ValueError: not enough values to unpack (expected 3, got 2)
Python báo ValueError vì có ba tên nhận nhưng chỉ có hai giá trị. Hãy dùng hai tên hoặc đổi số giá trị mà hàm trả về cho phù hợp.
Biến cục bộ thuộc lần gọi; biến toàn cục ở ngoài hàm
Hãy theo dõi tham số và biến được tạo bên trong hàm:
def tinh_tien(so_luong):
tien = so_luong * 8
print(tien)
tinh_tien(2)
tinh_tien(5)16 40
so_luong và tien ở đây là biến cục bộ (local variable) của hàm. Mỗi lần gọi có giá trị riêng: lần đầu tien là 16, lần sau là 40. Tên cục bộ không dùng trực tiếp được ở ngoài hàm.
def tinh_tien(so_luong):
tien = so_luong * 8
return tien
tinh_tien(2)
print(tien)Lỗi khi chạy, ở dòng 6
NameError: name 'tien' is not defined
Ví dụ báo NameError tại print(tien). Muốn dùng số tiền bên ngoài, hãy nhận return vào một biến ở nơi gọi.
Tên được gán ở ngoài hàm là biến toàn cục (global variable) trong chương trình này. Một hàm có thể đọc tên toàn cục nếu không có tên cục bộ tương ứng:
don_gia = 8
def tinh_tien(so_luong):
return so_luong * don_gia
print(tinh_tien(3))24
Ở đây don_gia được đọc từ bên ngoài. Tuy nhiên, điều đó làm hàm phụ thuộc vào một tên ở nơi khác. Bạn khó nhận ra đủ dữ liệu cần thiết khi chỉ nhìn lời gọi.
Nếu gán một tên trong hàm, Python coi tên đó là cục bộ trong hàm, trừ khi có khai báo đặc biệt. Tên cục bộ có thể trùng tên toàn cục mà không đổi tên bên ngoài:
diem = 7
def vi_du():
diem = 9
print(diem)
vi_du()
print(diem)9 7
Trong hàm, diem là 9; ở ngoài, diem vẫn là 7. Phạm vi cho biết nơi một tên có thể được tìm thấy. Nếu đọc rồi mới gán cùng tên trong hàm, Python vẫn coi tên ấy là cục bộ:
diem = 7
def tang_diem():
diem = diem + 1
return diem
print(tang_diem())Lỗi khi chạy, ở dòng 4
UnboundLocalError: cannot access local variable 'diem' where it is not associated with a value
Lời gọi báo UnboundLocalError: diem cục bộ chưa nhận giá trị khi vế phải được tính. Khai báo global diem có thể cho phép hàm gán tên toàn cục, nhưng việc thay đổi dữ liệu ở xa khiến chương trình khó theo dõi.
Để thấy tác dụng của global, hãy chạy ví dụ nhỏ sau. Dòng khai báo cho biết phép gán diem trong hàm dùng tên ở ngoài hàm:
diem = 7
def tang_diem():
global diem
diem = diem + 1
tang_diem()
print(diem)8
Sau lời gọi, biến ngoài hàm là 8. Điều này khiến tác động của hàm khó thấy tại nơi gọi. Hãy ưu tiên truyền và nhận giá trị tường minh:
def tang_diem(diem):
return diem + 1
diem = 7
diem = tang_diem(diem)
print(diem)8
Bạn nhìn thấy giá trị được đưa vào và kết quả được gán lại. Nếu bỏ phép gán ở ngoài, lời gọi không tự thay đổi biến diem bên ngoài.
Hàm gọi hàm giúp kết hợp các bước kiểm tra
Một số nguyên tố là số nguyên lớn hơn 1 và chỉ chia hết cho 1 và chính nó. Hàm dưới đây thử các ước từ 2 đến trước số cần kiểm tra. Khi gặp ước, hàm có thể trả về ngay.
def la_so_nguyen_to(n):
if n < 2:
return False
for uoc in range(2, n):
if n % uoc == 0:
return False
return True
print(la_so_nguyen_to(1))
print(la_so_nguyen_to(7))
print(la_so_nguyen_to(9))False True False
1 bị loại trước vòng lặp. Với 7, không ước nào được thử chia hết nên hàm trả True. Với 9, ước 3 làm hàm trả False ngay. Với 2, range(2, 2) rỗng và hàm trả True.
Bây giờ dùng hàm kiểm tra trong một hàm khác để đếm số nguyên tố từ 1 đến giới hạn:
def la_so_nguyen_to(n):
if n < 2:
return False
for uoc in range(2, n):
if n % uoc == 0:
return False
return True
def dem_nguyen_to(gioi_han):
dem = 0
for so in range(1, gioi_han + 1):
if la_so_nguyen_to(so):
dem = dem + 1
return dem
print(dem_nguyen_to(10))4
Mỗi lần lặp, dem_nguyen_to gọi la_so_nguyen_to(so). Kết quả True hoặc False quyết định có tăng dem không. Sau vòng lặp, hàm đếm trả kết quả cho print. Trong khoảng từ 1 đến 10 có bốn số nguyên tố: 2, 3, 5, 7.
Hàm đếmChọn số cần xétHàm kiểm traTrả True hoặc FalseHàm đếmTăng đếm nếu kết quả là True
Đặt return True sau vòng lặp, không đặt ngay sau lần thử đầu tiên. Nếu trả quá sớm, một số như 9 có thể bị kết luận nguyên tố chỉ vì không chia hết cho 2.
Lỗi thường gặp
| Lỗi | Vì sao sai | Cách sửa |
|---|---|---|
Dùng kết quả hàm chỉ print để tính | Hàm trả None | Trả số bằng return |
Đặt lệnh cần chạy sau return | Lần gọi đã kết thúc | Chuyển lệnh lên trước return |
Một nhánh quên return | Nhánh ấy có thể trả None | Xét từng đường đi và trả kết quả phù hợp |
| Nhận hai giá trị vào ba biến | Số lượng không khớp | Dùng đúng hai biến |
| Dùng tên cục bộ ngoài hàm | Tên không tồn tại ở nơi gọi | Trả giá trị rồi nhận vào biến |
| Đọc biến cục bộ trước khi gán | Biến chưa có giá trị trong lần gọi | Truyền dữ liệu bằng tham số |
| Kết luận nguyên tố sau lần thử đầu | Chưa thử hết các ước cần xét | Trả True sau vòng lặp |
Tóm tắt
returnkết thúc lần gọi và gửi giá trị về nơi gọi;printhiển thị.- Hàm đi tới cuối hoặc dùng
returnkhông giá trị trảNone. - Các giá trị sau
returncó thể được đóng gói và nhận theo thứ tự. - Biến cục bộ thuộc hàm; truyền tham số và trả kết quả giúp tránh phụ thuộc toàn cục.
- Một hàm có thể gọi hàm khác và dùng kết quả để quyết định bước tiếp theo.