Bài 2.11 - Hàm trong Python (def)

Bài 2.11 - Hàm trong Python (def)
Chào mừng bạn đến với thế giới hàm trong Python! Nếu bạn đã từng viết một đoạn code và thấy mình lặp đi lặp lại cùng một logic ở nhiều nơi, thì hàm chính là vị cứu tinh bạn đang tìm kiếm. Hàm giúp chúng ta đóng gói một khối code có thể tái sử dụng, làm cho chương trình trở nên ngăn nắp, dễ đọc và dễ bảo trì hơn rất nhiều.
Hãy tưởng tượng hàm như một công thức nấu ăn: bạn định nghĩa nó một lần (viết công thức), và sau đó bạn có thể "gọi" nó bất cứ khi nào bạn muốn nấu món đó mà không cần viết lại toàn bộ các bước.
Trong Python, chúng ta sử dụng từ khóa def
để định nghĩa một hàm.
1. Định nghĩa và Gọi hàm cơ bản
Cú pháp cơ bản để định nghĩa một hàm trông như thế này:
def ten_ham():
# Khối lệnh của hàm (phải thụt vào)
print("Chào mừng bạn đến với hàm trong Python!")
# ... các lệnh khác ...
def
: Là từ khóa báo hiệu cho Python biết rằng bạn đang định nghĩa một hàm.ten_ham
: Là tên bạn đặt cho hàm của mình. Quy tắc đặt tên hàm giống như quy tắc đặt tên biến (nên dùng chữ thường và dấu gạch dưới_
để nối các từ).()
: Cặp dấu ngoặc đơn, nơi chúng ta sẽ đặt các tham số (inputs) cho hàm sau này. Kể cả khi hàm không cần input, bạn vẫn phải có cặp ngoặc này.:
: Dấu hai chấm kết thúc dòng định nghĩa hàm.- Khối lệnh thụt vào: Tất cả các dòng code thuộc về hàm phải được thụt vào một mức so với dòng
def
. Đây là cách Python xác định phạm vi của hàm.
Sau khi định nghĩa hàm, nó chưa tự chạy đâu nhé. Bạn cần phải gọi hàm (function call) để thực thi các lệnh bên trong nó:
# Định nghĩa hàm
def loi_chao():
print("Xin chào!")
print("Hàm này thật đơn giản phải không?")
# Gọi hàm (thực thi code bên trong hàm)
print("Chuẩn bị gọi hàm...")
loi_chao()
print("Đã gọi hàm xong!")
Giải thích:
- Chúng ta định nghĩa hàm
loi_chao
với hai lệnhprint
. - Chương trình chạy đến dòng
print("Chuẩn bị gọi hàm...")
và in ra màn hình. - Dòng
loi_chao()
là lời gọi hàm. Lúc này, luồng thực thi sẽ nhảy vào bên trong hàmloi_chao
. - Hai lệnh
print
bên trong hàmloi_chao
được thực thi, in ra "Xin chào!" và "Hàm này thật đơn giản phải không?". - Sau khi thực thi xong lệnh cuối cùng trong hàm, luồng thực thi quay trở lại vị trí ngay sau lời gọi hàm.
- Dòng
print("Đã gọi hàm xong!")
được thực thi.
Kết quả bạn thấy sẽ là:
Chuẩn bị gọi hàm...
Xin chào!
Hàm này thật đơn giản phải không?
Đã gọi hàm xong!
2. Hàm với Tham số (Parameters) và Đối số (Arguments)
Hầu hết các hàm cần nhận dữ liệu đầu vào để xử lý. Chúng ta định nghĩa các tham số (parameters) bên trong cặp dấu ngoặc đơn ()
khi định nghĩa hàm. Khi gọi hàm, chúng ta truyền các đối số (arguments) tương ứng với các tham số đó.
- Tham số (Parameter): Là biến được liệt kê bên trong dấu ngoặc đơn trong phần định nghĩa hàm. Nó giống như một chỗ giữ chỗ cho giá trị sẽ được truyền vào.
- Đối số (Argument): Là giá trị thực tế được gửi đến hàm khi nó được gọi.
Hãy xem ví dụ:
# Định nghĩa hàm 'chao_ban' với một tham số là 'ten'
def chao_ban(ten):
print(f"Xin chào, {ten}!")
print(f"Rất vui được gặp bạn, {ten}.")
# Gọi hàm với các đối số khác nhau
chao_ban("Alice") # "Alice" là đối số được truyền cho tham số 'ten'
print("---")
chao_ban("Bob") # "Bob" là đối số được truyền cho tham số 'ten'
Giải thích:
- Hàm
chao_ban
được định nghĩa với một tham số làten
. Bên trong hàm, biếnten
này có thể được sử dụng như một biến bình thường. - Khi gọi
chao_ban("Alice")
, giá trị chuỗi"Alice"
(đối số) được gán cho tham sốten
. Hàm sẽ thực thi và in ra lời chào dành cho Alice. - Tương tự, khi gọi
chao_ban("Bob")
, giá trị"Bob"
được gán choten
, và hàm in ra lời chào cho Bob.
Bạn có thể định nghĩa hàm với nhiều tham số:
# Định nghĩa hàm tính tổng hai số
def tinh_tong(so_a, so_b):
tong = so_a + so_b
print(f"Tổng của {so_a} và {so_b} là: {tong}")
# Gọi hàm
tinh_tong(5, 3) # 5 là đối số cho so_a, 3 là đối số cho so_b
tinh_tong(100, -20) # 100 là đối số cho so_a, -20 là đối số cho so_b
Giải thích:
- Hàm
tinh_tong
nhận hai tham sốso_a
vàso_b
. - Khi gọi
tinh_tong(5, 3)
,so_a
nhận giá trị5
vàso_b
nhận giá trị3
. Hàm tính tổng và in kết quả. - Thứ tự của các đối số khi gọi hàm (gọi là đối số vị trí - positional arguments) rất quan trọng. Đối số đầu tiên sẽ được gán cho tham số đầu tiên, đối số thứ hai cho tham số thứ hai, và cứ thế.
3. Giá trị trả về (Return Value)
Thường thì, chúng ta muốn hàm không chỉ thực hiện một hành động (như print
) mà còn trả về một kết quả để có thể sử dụng ở nơi khác trong chương trình. Từ khóa return
được sử dụng cho mục đích này.
Khi gặp lệnh return
, hàm sẽ ngay lập tức kết thúc và gửi giá trị được chỉ định sau return
về nơi đã gọi nó.
# Định nghĩa hàm tính tổng và trả về kết quả
def tinh_tong_tra_ve(so_a, so_b):
tong = so_a + so_b
return tong # Trả về giá trị của biến 'tong'
# Gọi hàm và lưu kết quả trả về vào một biến
ket_qua_1 = tinh_tong_tra_ve(10, 25)
ket_qua_2 = tinh_tong_tra_ve(-5, 12)
print(f"Kết quả lần 1 là: {ket_qua_1}")
print(f"Kết quả lần 2 là: {ket_qua_2}")
# Bạn cũng có thể dùng trực tiếp giá trị trả về
print(f"Tổng của 1 và 2 là: {tinh_tong_tra_ve(1, 2)}")
Giải thích:
- Hàm
tinh_tong_tra_ve
tính tổng củaso_a
vàso_b
. - Lệnh
return tong
làm hai việc:- Kết thúc hàm ngay lập tức.
- Gửi giá trị hiện tại của biến
tong
trở lại nơi hàm được gọi.
- Trong dòng
ket_qua_1 = tinh_tong_tra_ve(10, 25)
, hàm được gọi vớiso_a=10
,so_b=25
. Hàm tính ratong=35
vàreturn 35
. Giá trị35
này sau đó được gán cho biếnket_qua_1
. - Tương tự với
ket_qua_2
. - Bạn có thể thấy, hàm
tinh_tong_tra_ve
không tựprint
kết quả. Nó chỉ trả về giá trị, cho phép bạn linh hoạt hơn trong việc sử dụng kết quả đó (lưu vào biến, dùng trong biểu thức khác, in ra...).
Lưu ý quan trọng:
- Một hàm có thể có nhiều lệnh
return
(ví dụ trong các nhánhif
/else
), nhưng chỉ có một lệnhreturn
được thực thi trong một lần gọi hàm (vì hàm kết thúc ngay sau khi gặpreturn
). - Nếu một hàm không có lệnh
return
nào, hoặc có lệnhreturn
mà không có giá trị theo sau (chỉreturn
), nó sẽ ngầm định trả về một giá trị đặc biệt làNone
.
def ham_khong_tra_ve():
print("Hàm này không có lệnh return rõ ràng.")
# Python sẽ ngầm định 'return None' ở cuối hàm
def ham_tra_ve_none():
print("Hàm này return None tường minh.")
return None
ket_qua_none_1 = ham_khong_tra_ve()
ket_qua_none_2 = ham_tra_ve_none()
print(f"Kết quả từ hàm không return: {ket_qua_none_1}")
print(f"Kết quả từ hàm return None: {ket_qua_none_2}")
Giải thích:
Cả hai hàm trên, khi được gọi, đều sẽ trả về giá trị None
. None
là một đối tượng đặc biệt trong Python, thường được dùng để biểu thị sự "không có giá trị" hoặc "rỗng".
4. Docstrings - Tài liệu cho Hàm
Việc viết tài liệu mô tả hàm làm gì, các tham số của nó là gì và nó trả về cái gì là cực kỳ quan trọng, đặc biệt khi làm việc nhóm hoặc khi bạn xem lại code của chính mình sau một thời gian. Python có một cơ chế tích hợp cho việc này gọi là docstring.
Docstring là một chuỗi ký tự (string literal) xuất hiện ngay dòng đầu tiên bên trong phần thân hàm (sau dấu :
). Người ta thường dùng cặp ba dấu nháy kép """
hoặc ba dấu nháy đơn '''
để tạo docstring, đặc biệt nếu nó dài nhiều dòng.
def tinh_luy_thua(co_so, so_mu):
"""
Tính lũy thừa của một cơ số với số mũ cho trước.
Args:
co_so (int, float): Cơ số.
so_mu (int, float): Số mũ.
Returns:
int, float: Kết quả của co_so^so_mu.
None: Nếu số mũ không phải là số nguyên không âm (ví dụ).
(Đây chỉ là ví dụ, hàm pow() thực tế phức tạp hơn)
"""
# (Trong ví dụ này, chúng ta dùng toán tử **)
if isinstance(so_mu, int) and so_mu >= 0:
return co_so ** so_mu
else:
print("Số mũ phải là số nguyên không âm.")
return None # Trả về None nếu đầu vào không hợp lệ
# Cách xem docstring:
help(tinh_luy_thua)
# Hoặc trong môi trường như Jupyter Notebook/IPython:
# tinh_luy_thua?
Giải thích:
- Chuỗi nằm giữa
""" """
là docstring của hàmtinh_luy_thua
. - Dòng đầu tiên thường là một tóm tắt ngắn gọn về chức năng của hàm.
- Các dòng tiếp theo mô tả chi tiết hơn, thường bao gồm:
Args:
hoặcParameters:
: Mô tả từng tham số, kiểu dữ liệu mong đợi và ý nghĩa của nó.Returns:
: Mô tả giá trị hàm trả về, kiểu dữ liệu và ý nghĩa.Raises:
(Tùy chọn): Mô tả các lỗi (exceptions) mà hàm có thể ném ra.
- Lệnh
help(ten_ham)
sẽ in ra docstring này, giúp người khác (và chính bạn) hiểu cách sử dụng hàm mà không cần đọc code bên trong.
Hãy tập thói quen viết docstring cho mọi hàm bạn viết!
5. Phạm vi Biến (Variable Scope)
Đây là một khái niệm rất quan trọng khi làm việc với hàm. Phạm vi của một biến xác định nơi mà biến đó có thể được truy cập hoặc sửa đổi trong chương trình.
- Biến cục bộ (Local Variable): Là biến được định nghĩa bên trong một hàm. Nó chỉ tồn tại và chỉ có thể được truy cập từ bên trong hàm đó mà thôi. Khi hàm kết thúc, biến cục bộ sẽ bị hủy. Tham số của hàm cũng được coi là biến cục bộ.
- Biến toàn cục (Global Variable): Là biến được định nghĩa bên ngoài tất cả các hàm, ở cấp độ cao nhất của file script. Biến toàn cục có thể được truy cập (đọc giá trị) từ bất kỳ đâu trong file, kể cả bên trong các hàm.
Hãy xem ví dụ:
bien_toan_cuc = "Tôi là biến toàn cục"
def ham_vi_du():
bien_cuc_bo = "Tôi là biến cục bộ"
print(f"Bên trong hàm: {bien_cuc_bo}")
# Có thể đọc biến toàn cục từ bên trong hàm
print(f"Bên trong hàm, đọc biến toàn cục: {bien_toan_cuc}")
# Gọi hàm
ham_vi_du()
# In biến toàn cục từ bên ngoài hàm (OK)
print(f"Bên ngoài hàm: {bien_toan_cuc}")
# Cố gắng truy cập biến cục bộ từ bên ngoài hàm (SẼ GÂY LỖI!)
# print(f"Bên ngoài hàm, thử đọc biến cục bộ: {bien_cuc_bo}") # Dòng này sẽ gây NameError
Giải thích:
bien_toan_cuc
được định nghĩa bên ngoài hàm, nên nó là biến toàn cục.bien_cuc_bo
được định nghĩa bên trongham_vi_du
, nên nó là biến cục bộ.- Bên trong
ham_vi_du
:- Chúng ta có thể truy cập
bien_cuc_bo
(vì nó được định nghĩa ở đó). - Chúng ta cũng có thể đọc giá trị của
bien_toan_cuc
.
- Chúng ta có thể truy cập
- Bên ngoài hàm:
- Chúng ta có thể truy cập
bien_toan_cuc
. - Chúng ta không thể truy cập
bien_cuc_bo
. Nếu bạn bỏ dấu#
ở dòng cuối cùng và chạy, Python sẽ báo lỗiNameError: name 'bien_cuc_bo' is not defined
vì biến này không tồn tại ở phạm vi toàn cục.
- Chúng ta có thể truy cập
Sửa đổi biến toàn cục từ bên trong hàm:
Mặc định, bạn chỉ có thể đọc biến toàn cục từ bên trong hàm. Nếu bạn cố gắng gán một giá trị mới cho biến toàn cục bên trong hàm, Python sẽ mặc định tạo ra một biến cục bộ mới có cùng tên, che đi biến toàn cục.
x = 10 # Biến toàn cục
def thu_thay_doi_x():
x = 5 # Python tạo ra một biến cục bộ 'x' mới
print(f"Bên trong hàm, x = {x}") # In ra 5
print(f"Trước khi gọi hàm, x = {x}") # In ra 10
thu_thay_doi_x()
print(f"Sau khi gọi hàm, x = {x}") # Vẫn in ra 10! Biến toàn cục không bị thay đổi.
Nếu bạn thực sự muốn sửa đổi biến toàn cục từ bên trong hàm, bạn cần sử dụng từ khóa global
:
y = 100 # Biến toàn cục
def thay_doi_y_thuc_su():
global y # Báo cho Python biết chúng ta muốn dùng biến toàn cục 'y'
y = 50 # Gán giá trị mới cho biến toàn cục 'y'
print(f"Bên trong hàm, y = {y}") # In ra 50
print(f"Trước khi gọi hàm, y = {y}") # In ra 100
thay_doi_y_thuc_su()
print(f"Sau khi gọi hàm, y = {y}") # In ra 50! Biến toàn cục đã bị thay đổi.
Cảnh báo: Nên hạn chế tối đa việc sử dụng từ khóa global
. Việc thay đổi biến toàn cục từ bên trong hàm có thể làm code trở nên khó hiểu và khó gỡ lỗi, vì trạng thái của chương trình có thể bị thay đổi từ những nơi không ngờ tới. Cách tốt hơn thường là sử dụng tham số và giá trị trả về để truyền dữ liệu ra vào hàm.
6. Tham số với Giá trị Mặc định (Default Parameter Values)
Bạn có thể cung cấp giá trị mặc định cho các tham số khi định nghĩa hàm. Nếu khi gọi hàm, người dùng không cung cấp đối số cho tham số đó, giá trị mặc định sẽ được sử dụng.
def chao_hoi(ten, loi_chao="Xin chào"):
"""Chào hỏi ai đó với một lời chào tùy chỉnh hoặc mặc định."""
print(f"{loi_chao}, {ten}!")
# Gọi hàm chỉ với tham số bắt buộc 'ten'
chao_hoi("Lan") # Sử dụng loi_chao mặc định là "Xin chào"
# Gọi hàm và cung cấp cả hai đối số
chao_hoi("Minh", "Chào buổi sáng") # Ghi đè giá trị mặc định
# Gọi hàm và cung cấp cả hai đối số
chao_hoi("Hoa", loi_chao="Chúc ngủ ngon") # Có thể dùng keyword argument
Giải thích:
- Trong định nghĩa
def chao_hoi(ten, loi_chao="Xin chào"):
, tham sốloi_chao
có giá trị mặc định là"Xin chào"
. - Tham số
ten
không có giá trị mặc định, nên nó là tham số bắt buộc. - Khi gọi
chao_hoi("Lan")
, chỉ có đối số choten
được cung cấp.loi_chao
sẽ tự động lấy giá trị mặc định. - Khi gọi
chao_hoi("Minh", "Chào buổi sáng")
, cả hai đối số được cung cấp, ghi đè lên giá trị mặc định.
Quan trọng: Các tham số có giá trị mặc định phải được đặt sau tất cả các tham số không có giá trị mặc định trong danh sách tham số.
# SAI: Tham số không mặc định sau tham số mặc định
# def ham_loi(gia_tri_mac_dinh="abc", gia_tri_bat_buoc):
# pass # SyntaxError: non-default argument follows default argument
# ĐÚNG: Tham số bắt buộc trước, tham số mặc định sau
def ham_dung(gia_tri_bat_buoc, gia_tri_mac_dinh="abc"):
print(f"Bắt buộc: {gia_tri_bat_buoc}, Mặc định: {gia_tri_mac_dinh}")
ham_dung(10) # Mặc định: abc
ham_dung(20, "xyz") # Mặc định: xyz
7. Đối số Từ khóa (Keyword Arguments)
Khi gọi hàm, bạn có thể chỉ định đối số bằng tên tham số của chúng. Điều này được gọi là đối số từ khóa (keyword arguments). Nó có thể giúp code dễ đọc hơn và cho phép bạn truyền đối số không theo thứ tự ban đầu.
def mo_ta_thu_cung(loai_vat, ten_thu_cung):
"""Mô tả thông tin về thú cưng."""
print(f"Tôi có một con {loai_vat}.")
print(f"Tên của nó là {ten_thu_cung}.")
# Cách gọi thông thường (đối số vị trí)
mo_ta_thu_cung("mèo", "Miu")
print("---")
# Cách gọi dùng đối số từ khóa
mo_ta_thu_cung(loai_vat="chó", ten_thu_cung="Lu")
print("---")
# Dùng đối số từ khóa, thứ tự không quan trọng
mo_ta_thu_cung(ten_thu_cung="Vẹt", loai_vat="chim")
Giải thích:
- Khi dùng
loai_vat="chó"
, bạn đang nói rõ ràng rằng giá trị"chó"
là dành cho tham sốloai_vat
. - Việc sử dụng đối số từ khóa giúp code rõ ràng hơn, đặc biệt khi hàm có nhiều tham số hoặc các tham số có ý nghĩa không rõ ràng nếu chỉ dựa vào vị trí.
- Bạn có thể kết hợp đối số vị trí và đối số từ khóa, nhưng tất cả các đối số vị trí phải đứng trước tất cả các đối số từ khóa.
# Đúng: vị trí trước, từ khóa sau
mo_ta_thu_cung("chuột hamster", ten_thu_cung="Bông")
# Sai: từ khóa trước vị trí
# mo_ta_thu_cung(loai_vat="cá", "Vàng") # SyntaxError: positional argument follows keyword argument
Hàm là một công cụ vô cùng mạnh mẽ trong Python và mọi ngôn ngữ lập trình khác. Việc thành thạo cách định nghĩa, sử dụng tham số, giá trị trả về và hiểu rõ phạm vi biến sẽ giúp bạn viết code hiệu quả, dễ đọc và có khả năng tái sử dụng cao hơn đáng kể. Hãy thực hành viết nhiều hàm khác nhau để làm quen nhé!
Comments