Học bằng cách đọc theo thứ tự

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.

Ba pattern hàm bậc cao bao phủ
①Gán hàmvào biếnf = printf("HELLO")②Truyền hàmlàm tham sốafter(2, greet)→ callback③Trả về hàmlàm giá trịmake_greeter("Minh")→ trả về một hàm
Vì hàm là giá trị, bạn có thể dùng chúng theo ba cách: gán biến, truyền tham số, và giá trị trả về.

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")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.

Tên hàm chỉ là một cái tên trỏ tới đối tượng hàm
print(tên built-in)đối tượng hàm(thân hàm)OutputHELLOa(bí danh tự đặt)tham chiếucùng tham chiếuprint("HELLO")a("HELLO")
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.

Cảm nhận hàm như giá trị, với cả hàm tự viết và hàm built-in.

① Định nghĩa def greet(): với thân là print("Xin chào").

② Tạo bí danh bằng say = greet và gọi say() — xác nhận bạn có cùng kết quả với greet().

③ Đặt bí danh cho built-in len bằng f = len, và in kết quả của f("Python") bằng print(...).

(Khi đáp án đúng, phần giải thích sẽ xuất hiện.)

Python Editor

Chạy code để xem đầu ra

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!
Callback nghĩa là "Truyền hành vi bên trong vào"
Phía bên gọigreet_all(names, formal)Hàm bậc caogreet_all(names, formatter)Với mỗi name,gọi formatter(name)Callbackformal(name)"Minh ơi, ..."3 dòng outputtruyền hàmthân hàmcó thể đổikết quả
Thân 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.

Xây một hàm bậc cao cho phép bên gọi đổi format thông báo cho danh sách username.

① Định nghĩa def notify_all(users, formatter):. Với for user in users:, gọi print(formatter(user)) cho mỗi user.

② Xây def login_alert(name): trả về f"[Đăng nhập] {name} đã vào".

③ Xây def logout_alert(name): trả về f"[Đăng xuất] {name} đã ra".

④ Khởi tạo users = ["Minh", "Linh"], rồi gọi notify_all(users, login_alert)notify_all(users, logout_alert).

Python Editor

Chạy code để xem đầu ra

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ẻ
Đổi callback dựa trên điều kiện
process_number(7,handle_even,handle_odd)n % 2 == 0?even_callback(n)→ handle_evenodd_callback(n)→ handle_oddquyết địnhTrueFalse

Xây một hàm bậc cao định tuyến xử lý thanh toán tới một callback khi thành công và một callback khác khi thất bại.

① Định nghĩa def process_payment(amount, on_success, on_failure):. Nếu amount > 0, gọi on_success(amount); ngược lại gọi on_failure(amount).

② Xây def notify_success(amount): in f"Thanh toán {amount} yên đã hoàn tất".

③ Xây def notify_failure(amount): in f"Số tiền {amount} không hợp lệ".

④ Gọi process_payment(1500, notify_success, notify_failure)process_payment(0, notify_success, notify_failure).

Python Editor

Chạy code để xem đầu ra

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 INFOmake_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
make_logger trả về một "hàm đã cấu hình sẵn"
Module (Không gian tên Global)
  • 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
Frame của make_logger("INFO")
  • Giữ prefix = "INFO"
  • return hàm log định nghĩa bên trong
log (vẫn nhớ INFO)
  • In [INFO] ... mỗi lần gọi
Frame của make_logger("ERROR")
  • Giữ prefix = "ERROR"
  • return một hàm log riêng
log (vẫn nhớ ERROR)
  • Một đối tượng hàm riêng, độc lập với info
Mỗi lần gọi make_logger xây một hàm log riêng nhớ prefix của nó và trả về. Bên gọi dùng chúng dưới các tên ngắn như info() / error().

Xây một hàm bậc cao trả về hàm gắn tag nhớ tag của nó.

① Định nghĩa def make_tagger(tag):. Bên trong, định nghĩa def tag_text(text): trả về f"<{tag}>{text}</{tag}>".

② Cuối cùng, trả về hàm bên trong bằng return tag_text.

③ Xây hai hàm: b = make_tagger("b")i = make_tagger("i").

④ Gọi print(b("Quan trọng"))print(i("Nhấn mạnh")) và xác nhận <b>Quan trọng</b> / <i>Nhấn mạnh</i> xuất hiện tương ứng.

Python Editor

Chạy code để xem đầu ra
QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1Code này in ra gì?
def greet():
print("Hi")
f = greet
f()

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")