Câu 1Code này in ra gì?def greet():
print("Hi")
f = greet
f()
Hàm bậc cao — Coi hàm như tham số và giá trị trả về
Học hàm bậc cao trong Python: coi hàm như tham số hay giá trị trả về để dựng callback và closure linh hoạt cho code của bạn.
Trong bài trước về yield, bạn đã thấy các hàm tạo giá trị từng cái một. Lần này bạn tiếp tục đi sâu vào phía hàm và xem hàm bậc cao — cơ chế cho phép bạn coi chính hàm cũng là một giá trị.
Ba pattern cần nhớ: gán vào biến, truyền như tham số (một callback), và trả về như giá trị.
Hàm bậc cao là gì — Hàm cũng là giá trị
Trong Python, hàm cũng là giá trị giống như số nguyên hay chuỗi. Bạn có thể đặt chúng vào biến, truyền cho hàm khác làm tham số, và trả về làm kết quả.
Một hàm nhận hàm làm tham số hoặc trả về hàm như kết quả được gọi là hàm bậc cao. Khác với print() hay len() chỉ thao tác trên giá trị, hàm bậc cao lắp ráp các mảnh hành vi.
Gán hàm vào biến — Hàm cũng là giá trị được tham chiếu
Khi bạn định nghĩa một hàm bằng def, thân hàm được tạo trong bộ nhớ và tên hàm trỏ tới vị trí đó. Gán bằng a = print, và a giờ trỏ tới cùng thân hàm, nên a("HELLO") và print("HELLO") làm chính xác điều giống nhau.
Mẹo là bỏ () sau tên hàm. Thêm () và bạn gọi hàm thay vì gán, và giá trị trả về được gán.
a = print # không có () — gán chính thân hàm vào a
a("HELLO") # HELLO ← giống print("HELLO")
print(id(print)) # vd. 4395020128
print(id(a)) # cùng địa chỉ hiện ra
# Hàm bạn tự định nghĩa cũng vậy
def greet():
print("Xin chào")
say = greet # tạo bí danh say
say() # Xin chào
Có hay không có () thay đổi ý nghĩa
a = print là dạng truyền chính hàm, còn a = print("HELLO") là dạng gọi hàm và truyền kết quả. Trong trường hợp thứ hai, giá trị trả về của print("HELLO") (None) rơi vào a, nên gọi a() sẽ phát sinh TypeError: 'NoneType' object is not callable.
Truyền hàm như tham số — Callback
Cách dùng điển hình nhất của hàm bậc cao là callback — "một hàm được truyền cho hàm khác và được gọi tại một thời điểm cụ thể", trong đó "bên gọi quyết định hành vi thực sự".
Ví dụ, giả sử bạn có một thủ tục "in lời chào cho danh sách ba cái tên", nhưng bạn muốn chỉ thay đổi kiểu chào từ phía gọi. Giữ thân hàm là vòng lặp lấy tên từng cái một, và nhận phần định dạng làm tham số hàm — bên gọi có thể tái dùng thủ tục chỉ bằng cách đổi mẫu chào.
def greet_all(names, formatter):
for name in names:
print(formatter(name))
def formal(name):
return f"{name} ơi, cảm ơn bạn như mọi khi."
def casual(name):
return f"Chào {name}!"
greet_all(["Minh", "Linh", "Nam"], formal)
# Minh ơi, cảm ơn bạn như mọi khi.
# Linh ơi, cảm ơn bạn như mọi khi.
# Nam ơi, cảm ơn bạn như mọi khi.
greet_all(["Minh", "Linh", "Nam"], casual)
# Chào Minh!
# Chào Linh!
# Chào Nam!
greet_all chỉ là vòng lặp; cách biến mỗi name thành dòng chào được giao cho tham số formatter.Rẽ nhánh dựa trên callback theo mục đích
Bạn không bị giới hạn ở việc truyền một callback. "Dùng hàm này khi thành công, hàm kia khi thất bại" cũng là dạng phù hợp tự nhiên — bất cứ đâu bạn muốn chọn giữa nhiều callback.
Ví dụ, khi bạn muốn chuyển hành vi dựa trên việc một số là chẵn hay lẻ, hàm quyết định có thể chỉ làm if-rẽ nhánh, và để xử lý thực tế cho hai hàm do bên gọi cung cấp.
def process_number(number, even_callback, odd_callback):
if number % 2 == 0:
even_callback(number)
else:
odd_callback(number)
def handle_even(n):
print(f"{n} là số chẵn")
def handle_odd(n):
print(f"{n} là số lẻ")
process_number(4, handle_even, handle_odd) # 4 là số chẵn
process_number(7, handle_even, handle_odd) # 7 là số lẻ
Trả về hàm như giá trị — Closure trong thực hành
Pattern hàm bậc cao còn lại là "trả về hàm như giá trị". Trả về một hàm bên trong với return đã được nói trong bài về closure; ở đây, trọng tâm là góc nhìn thực hành "trao cho bên gọi một hàm đã nhớ thiết lập của nó".
Ví dụ, khi bạn muốn sản xuất hàng loạt hàm log đóng dấu cùng prefix mỗi lần gọi, đặt make_logger("INFO") cho logger INFO và make_logger("ERROR") cho logger ERROR — và code phía gọi vẫn ngắn như info("Xử lý đã bắt đầu").
def make_logger(prefix):
def log(message):
print(f"[{prefix}] {message}")
return log # trả về chính hàm
info = make_logger("INFO")
error = make_logger("ERROR")
info("Xử lý đã bắt đầu") # [INFO] Xử lý đã bắt đầu
error("Kết nối thất bại") # [ERROR] Kết nối thất bại
info("Xử lý đã hoàn tất") # [INFO] Xử lý đã hoàn tất
- info = make_logger("INFO") — nhận một hàm nhớ INFO
- error = make_logger("ERROR") — nhận một hàm riêng nhớ ERROR
- Giữ
prefix = "INFO" returnhàmlogđịnh nghĩa bên trong
- In
[INFO] ...mỗi lần gọi
- Giữ
prefix = "ERROR" returnmột hàmlogriêng
- Một đối tượng hàm riêng, độc lập với
info
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Dòng nào truyền say_hi như một callback?
Câu 3info("OK") in ra gì sau khi code này chạy?def make_logger(prefix):
def log(message):
print(f"[{prefix}] {message}")
return log
info = make_logger("INFO")