Trong bài này
Lan chuẩn bị nhãn cho 3 kệ sách. Mỗi nhãn cần in tên kệ và số sách tương ứng. Lan muốn viết cách in nhãn một lần rồi dùng lại với dữ liệu khác nhau.
Định nghĩa hàm trước khi gọi để thực hiện công việc
Lan cần in một đường ngăn trước mỗi nhóm nhãn. Hãy chạy ví dụ:
def in_duong_ngan():
print("-----")
in_duong_ngan()
print("Ke sach")
in_duong_ngan()----- Ke sach -----
Hàm (function) là một nhóm lệnh được đặt tên để thực hiện một công việc. Dòng bắt đầu bằng def là định nghĩa hàm (function definition). Tên in_duong_ngan giúp bạn nhận ra công việc của hàm. Cặp ngoặc tròn và dấu : kết thúc dòng định nghĩa.
Lệnh print thụt vào thuộc thân hàm (function body). Việc định nghĩa chưa chạy lệnh trong thân. Mỗi dòng in_duong_ngan() là một lần gọi hàm (function call): Python thực hiện thân hàm rồi tiếp tục sau chỗ gọi. Hai lần gọi tạo ra hai đường ngăn.
Bạn có thể đọc lời gọi như một chỉ dẫn: đến hàm có tên này và thực hiện các lệnh đã ghi trong đó. Khi các lệnh trong thân hoàn tất, Python trở lại ngay sau lời gọi. Vì vậy print("Ke sach") nằm giữa hai lần in đường ngăn, dù định nghĩa nằm phía trên cả ba lệnh.
Tên hàm cho biết cần chạy phần nào; cặp ngoặc tròn cho biết đây là lời gọi. Định nghĩa def và lời gọi có vai trò khác nhau. Một chương trình có thể định nghĩa hàm một lần, sau đó gọi nhiều lần hoặc không gọi lần nào.
Định nghĩaGhi nhận tên và thân hàmGọiChuyển đến thân hàmThân hàmIn đường ngănTiếp tụcChạy lệnh sau lời gọi
Nếu chỉ định nghĩa mà chưa gọi, không có đường ngăn nào được in:
def in_duong_ngan():
print("-----")
print("San sang")San sang
Tên hàm tuân theo quy tắc đặt tên biến đã học. Tên nên mô tả việc làm và nhất quán giữa định nghĩa với lời gọi. Không đặt lời gọi trước khi Python gặp định nghĩa:
in_duong_ngan()
def in_duong_ngan():
print("-----")Lỗi khi chạy, ở dòng 1
NameError: name 'in_duong_ngan' is not defined
Python báo NameError vì lúc gọi, tên in_duong_ngan chưa được định nghĩa. Hãy đưa định nghĩa lên trước lời gọi. Nếu thiếu dấu : hoặc không thụt thân hàm, Python cũng không đọc được định nghĩa:
def in_duong_ngan():
print("-----")Lỗi khi chạy, ở dòng 2
IndentationError: expected an indented block after function definition on line 1
Tham số nhận giá trị khác nhau trong mỗi lần gọi
Thay vì viết riêng một hàm cho từng kệ, Lan cho tên kệ vào hàm:
def in_ke(ten):
print("Ke:", ten)
in_ke("Truyen")
in_ke("Khoa hoc")Ke: Truyen Ke: Khoa hoc
ten trong dòng def là tham số (parameter): tên đại diện cho dữ liệu hàm nhận. "Truyen" và "Khoa hoc" là các đối số (argument) được đưa vào hai lời gọi. Mỗi lần gọi, ten nhận giá trị tương ứng và lệnh trong thân in tên đó.
in_ke("Truyen")ten nhận "Truyen"in_ke("Khoa hoc")ten nhận "Khoa hoc"
Hình cho thấy mỗi lời gọi đưa một giá trị vào tham số ten. Tên tham số không phải chữ cố định được in ra. Nếu đặt "ten" trong dấu nháy ở thân hàm, Python in chữ ten, không in giá trị đã truyền.
Hai lần gọi là hai lần thực hiện riêng. Lần gọi sau không sửa dòng code của lần gọi trước; nó đưa dữ liệu mới cho cùng một công việc. Bạn có thể thay tên kệ ở lời gọi mà không phải viết lại lệnh in trong thân.
Phân biệt nơi viết cũng giúp tìm lỗi. Trong def in_ke(ten):, ten mô tả dữ liệu hàm cần nhận. Trong in_ke("Truyen"), chuỗi "Truyen" là dữ liệu cụ thể được cung cấp cho lần chạy đó. Nếu thiếu dữ liệu mà tham số yêu cầu, Python không thể bắt đầu thân hàm như dự định.
Đối số cũng có thể là biến hoặc biểu thức đã học. Python tính biểu thức trước khi đưa giá trị vào hàm:
def in_so_hop(so):
print("So hop:", so)
hop_sang = 4
hop_chieu = 3
in_so_hop(hop_sang + hop_chieu)So hop: 7
Tổng 4 + 3 được tính thành 7, rồi so nhận giá trị đó. Tên biến ở chỗ gọi không cần giống tên tham số. Hàm yêu cầu một đối số thì lời gọi không có đối số sẽ báo lỗi:
def in_ke(ten):
print("Ke:", ten)
in_ke()Lỗi khi chạy, ở dòng 4
TypeError: in_ke() missing 1 required positional argument: 'ten'
Thông báo TypeError cho biết thiếu đối số bắt buộc. Hãy kiểm tra danh sách tham số và đưa đủ giá trị vào lời gọi.
Nhiều tham số nhận đối số theo thứ tự
Mỗi nhãn cần cả tên kệ và số sách. Đặt hai tham số cách nhau bằng dấu phẩy:
def in_nhan(ten, so_sach):
print(ten, so_sach)
in_nhan("Truyen", 12)
in_nhan("Toan", 8)Truyen 12 Toan 8
Trong lời gọi đầu, "Truyen" tương ứng với ten, còn 12 tương ứng với so_sach. Đây là đối số theo vị trí (positional argument): vị trí quyết định tham số nhận giá trị. Dấu phẩy tách các đối số, không được tính là một giá trị riêng.
Khi thứ tự có ý nghĩa, đổi chỗ các đối số có thể cho kết quả khác dù chương trình vẫn chạy:
def in_con_lai(ban_dau, da_muon):
print(ban_dau - da_muon)
in_con_lai(15, 4)
in_con_lai(4, 15)11 -11
Lời gọi đầu in số sách còn lại là 11. Lời gọi thứ hai tính 4 - 15; đó không phải phép tính Lan cần. Hãy đọc tên tham số từ trái sang phải trước khi gọi.
Hai số đều là đối số hợp lệ về số lượng. Vì thế máy không tự phát hiện ý định bị đảo. Để tự kiểm tra, hãy chọn hai số khác nhau và tính bằng tay trước. Nếu chỉ thử hai số bằng nhau, việc đổi chỗ sẽ khó nhận ra.
Truyền thừa đối số cũng gây lỗi. Hàm dưới đây chỉ nhận hai giá trị:
def in_nhan(ten, so_sach):
print(ten, so_sach)
in_nhan("Truyen", 12, 3)Lỗi khi chạy, ở dòng 4
TypeError: in_nhan() takes 2 positional arguments but 3 were given
Python báo TypeError. Nếu cần dữ liệu thứ ba, hãy quyết định công việc của hàm rồi sửa cả định nghĩa và lời gọi cho phù hợp.
Giá trị mặc định được dùng khi bỏ qua đối số
Phần lớn kệ có nhãn loại Sach. Lan có thể đặt giá trị đó ngay trong định nghĩa:
def in_loai(ten, loai="Sach"):
print(ten, loai)
in_loai("Truyen")
in_loai("Dia", "Thiet bi")Truyen Sach Dia Thiet bi
loai="Sach" đặt giá trị mặc định (default value) cho tham số loai. Lời gọi đầu chỉ truyền ten, nên loai nhận "Sach". Lời gọi sau truyền cả hai đối số, nên loai nhận "Thiet bi" trong lần gọi đó.
Giá trị mặc định không ngăn bạn truyền một giá trị khác, kể cả số 0. Python dùng giá trị được truyền khi đối số có mặt:
def in_phi(so_tien=5):
print(so_tien)
in_phi()
in_phi(0)5 0
Lần đầu in 5; lần sau in 0.
Giá trị mặc định giúp lời gọi ngắn hơn khi nhiều trường hợp dùng chung một lựa chọn. Tuy nhiên, dữ liệu quan trọng vẫn nên được ghi rõ ở lời gọi khi điều đó giúp tránh nhầm lẫn. Hãy kiểm tra cả hai trường hợp: bỏ đối số và truyền một giá trị khác.
Tham số bắt buộc phải đứng trước tham số có mặc định trong cách định nghĩa này:
def in_loai(loai="Sach", ten):
print(ten, loai)Lỗi khi chạy, ở dòng 1
SyntaxError: parameter without a default follows parameter with a default
Python báo SyntaxError. Chuyển ten lên trước loai="Sach" để lời gọi theo vị trí không bị mơ hồ.
Đối số theo tên chỉ rõ tham số cần nhận giá trị
Khi nhãn có hai dữ liệu, bạn có thể ghi tên tham số ở lời gọi:
def in_nhan(ten, so_sach):
print(ten, so_sach)
in_nhan(so_sach=12, ten="Truyen")Truyen 12
Đây là đối số theo tên (keyword argument). Python ghép so_sach=12 với tham số so_sach và ten="Truyen" với ten. Các đối số theo tên có thể được viết khác thứ tự khai báo. Dấu = trong lời gọi gắn giá trị với tên tham số; nó không phải phép so sánh ==.
Có thể kết hợp đối số theo vị trí và theo tên. Hãy đặt các đối số theo vị trí trước:
def in_phieu(ten, so_luong, gia=5):
print(ten, so_luong * gia)
in_phieu("But", 3, gia=7)But 21
"But" vào ten, 3 vào so_luong, còn gia=7 thay mặc định.
Hãy ghép các đối số theo vị trí trước: giá trị đầu vào tham số đầu, giá trị thứ hai vào tham số thứ hai. Sau đó ghép các đối số có tên với đúng tham số được ghi. Khi một tên đã nhận giá trị ở bước đầu, đừng ghi lại tên đó ở bước sau.
Lời gọi không được cung cấp hai giá trị cho cùng một tham số:
def in_nhan(ten, so_sach):
print(ten, so_sach)
in_nhan("Truyen", ten="Toan", so_sach=12)Lỗi khi chạy, ở dòng 4
TypeError: in_nhan() got multiple values for argument 'ten'
Giá trị theo vị trí đã đưa vào ten; ten="Toan" lại chỉ định ten. Python báo TypeError. Hãy bỏ một trong hai đối số và chọn giá trị cần dùng.
Chia chương trình thành hàm nhỏ giúp dùng lại từng bước
Lan muốn in phiếu gồm một đường ngăn, một nhãn và một đường ngăn nữa. Mỗi hàm dưới đây làm một việc; phần chương trình bên dưới điều phối các lời gọi:
Truyen 12
def in_duong_ngan():
print("-----")
def in_nhan(ten, so_sach):
print(ten, so_sach)
ten_ke = input()
so_sach = int(input())
in_duong_ngan()
in_nhan(ten_ke, so_sach)
in_duong_ngan()----- Truyen 12 -----
Chương trình đọc hai dòng, chuyển số sách thành số nguyên rồi gọi ba lần theo thứ tự. Bạn có thể thay cách in đường ngăn ở một chỗ trong định nghĩa. Cả hai lời gọi sau đó đều dùng cách mới khi chạy lại chương trình.
Thứ tự lời gọi thể hiện thứ tự các phần của phiếu. Hai hàm không tự chạy chỉ vì nằm đầu chương trình. Cách tách này cũng cho phép dùng lại đường ngăn ở một vị trí khác mà không chép lại lệnh in.
Một hàm có thể chứa điều kiện hoặc vòng lặp đã học. Ví dụ sau in một hàng ký hiệu có độ dài được truyền vào.
Trước khi chạy, hãy dự đoán số dấu sao ở mỗi hàng. Đối số đầu tiên là 3, còn đối số thứ hai là 5. Số lượt lặp thay đổi theo dữ liệu của từng lần gọi.
def in_hang(so_o):
for _ in range(so_o):
print("*", end="")
print()
in_hang(3)
in_hang(5)*** *****
Mỗi lần gọi, vòng lặp chạy với số lượt tương ứng. print() sau vòng lặp kết thúc hàng hiện tại; nó vẫn nằm trong thân hàm. Nếu đặt dòng đó trong vòng lặp, mỗi ký hiệu sẽ nằm trên một dòng.
Đặt hàm theo công việc rõ ràng, chẳng hạn in nhãn hoặc in đường ngăn. Không cần tạo hàm chỉ để đổi tên một giá trị. Trong bài này các hàm trực tiếp in kết quả; cách đưa kết quả về nơi gọi bằng return được học ở Bài 8.
Lỗi thường gặp
| Lỗi | Vì sao sai | Cách sửa |
|---|---|---|
| Gọi hàm trước định nghĩa | Tên chưa được Python nhận biết | Đặt định nghĩa trước lời gọi |
Thiếu : sau dòng def | Đầu khối chưa hợp lệ | Thêm : |
| Không thụt thân hàm | Python không thấy khối lệnh | Thụt các lệnh thuộc thân |
| Bỏ đối số bắt buộc | Tham số chưa nhận giá trị | Truyền đủ đối số |
| Đảo hai đối số theo vị trí | Giá trị vào nhầm tham số | Kiểm tra thứ tự hoặc dùng tên |
| Đặt tham số bắt buộc sau mặc định | Định nghĩa không hợp lệ | Đặt tham số bắt buộc trước |
| Truyền hai giá trị cho một tham số | Python không biết chọn giá trị nào | Chỉ truyền một lần |
Tóm tắt
defđặt tên và định nghĩa thân hàm; lời gọi mới thực hiện thân.- Tham số là tên trong định nghĩa; đối số là giá trị ở lời gọi.
- Đối số theo vị trí ghép từ trái sang phải; đối số theo tên ghép theo tên.
- Tham số mặc định nhận giá trị mặc định khi lời gọi bỏ qua nó.
- Các hàm nhỏ có thể chứa những lệnh, điều kiện và vòng lặp đã học.