DHDataHọc
TheoryLesson 19/305 min read+10 XP

Định nghĩa hàm

Bạn sẽ học được

  • Cú pháp def, thân hàm và cách gọi.
  • Docstring và type hint — tài liệu ngay trong mã.
  • Vì sao hàm giúp tái sử dụng và đặt tên cho logic.
  • Khi nào tách một khối lệnh thành hàm.
  • Lỗi thường gặp khi định nghĩa và gọi hàm.

Khái niệm cốt lõi

Hàm là một khối lệnh có tên, nhận đầu vào (tham số) và trả đầu ra (return). Định nghĩa bằng def:

code
def chao(name, greeting="Xin chào"):
    """Trả câu chào cho một người."""
    return f"{greeting} {name}"

print(chao("An"))
print(chao("Bình", "Chào buổi sáng"))
code
Xin chào An
Chào buổi sáng Bình

Cấu trúc:

PhầnTrong ví dụ
Từ khoádef
Tên hàmchao
Tham sốname, greeting="Xin chào"
Dấu hai chấm:
Thân hàmCác dòng thụt lề bên dưới
DocstringChuỗi ba nháy đầu thân

Tên hàm dùng snake_case và thường bắt đầu bằng động từ: tinh_tong, doc_file, loc_don_hang. Tên rõ ràng giúp đọc code như đọc câu.

Gọi hàm

Gọi bằng tên + ngoặc tròn. Thiếu ngoặc là lỗi rất hay gặp:

code
def greet(name):
    return f"Xin chào {name}"

print(greet)          # in đối tượng hàm
print(greet("An"))    # gọi hàm
code
<function greet at 0x...>
Xin chào An

greet là đối tượng hàm; greet("An") mới là lời gọi. Khi thấy output dạng <function ...>, hãy kiểm tra dấu ngoặc.

Hàm phải được định nghĩa trước khi gọi trong luồng thực thi:

code
print(chao("An"))

def chao(name):
    return f"Xin chào {name}"
code
NameError: name 'chao' is not defined

Python thực thi file từ trên xuống; def tạo tên tại thời điểm chạy tới dòng đó. Đặt toàn bộ định nghĩa hàm ở đầu file, phần gọi ở cuối, hoặc trong if __name__ == "__main__":.

Docstring

Chuỗi ba nháy ngay sau def là docstring — tài liệu của hàm, truy cập được bằng __doc__ hoặc help():

code
def chao(name, greeting="Xin chào"):
    """Trả câu chào cho một người."""
    return f"{greeting} {name}"

print(chao.__doc__)
code
Trả câu chào cho một người.

Docstring không ảnh hưởng logic nhưng là tài liệu sống: hiện trong editor, trong help(), và trong tài liệu sinh tự động. Viết một dòng mô tả làm gì, không mô tả lặp lại tên hàm. Với hàm phức tạp, thêm mục tham số và giá trị trả về.

Type hint

Python không bắt buộc khai báo kiểu, nhưng có thể ghi chú:

code
def chao(name: str, greeting: str = "Xin chào") -> str:
    return f"{greeting} {name}"

Type hint không kiểm tra lúc chạy — truyền số vào name vẫn chạy đến khi f-string ép thành chuỗi. Giá trị của nó nằm ở editor (gợi ý, cảnh báo) và công cụ kiểm tra tĩnh như mypy/pyright. Với dự án nhỏ, hint ở chữ ký hàm là đủ; không cần hint mọi biến.

Khi nào tách hàm

Tách khi:

  • Cùng một khối logic xuất hiện từ hai lần trở lên.
  • Một đoạn code cần tên để người đọc hiểu ý định.
  • Muốn kiểm thử riêng một phần mà không chạy toàn bộ chương trình.
  • Thân hàm hiện tại quá dài, có thể chia thành các bước có nghĩa.

Không nên tách chỉ để code ngắn hơn. Một hàm một dòng gọi hàm khác có thể làm code khó theo dõi nếu tên không mang thêm thông tin.

Ví dụ trước và sau:

code
# Trước: trộn tính toán và in
rows = [("An", 85), ("Bình", 92)]
total = 0
for name, score in rows:
    total += score
print(f"Trung bình: {total / len(rows):.1f}")
code
# Sau: tách logic có tên
def diem_trung_binh(rows):
    if not rows:
        return 0.0
    return sum(score for _, score in rows) / len(rows)

print(f"Trung bình: {diem_trung_binh(rows):.1f}")

Hàm sau kiểm thử được với nhiều bộ dữ liệu, kể cả list rỗng, mà không cần đọc bàn phím hay bắt output.

Hàm lồng nhau

Có thể định nghĩa hàm trong hàm. Hàm trong chỉ dùng được trong phạm vi hàm ngoài, trừ khi được trả ra ngoài:

code
def tao_bo_dem():
    count = 0

    def dem():
        nonlocal count
        count += 1
        return count

    return dem

bo_dem = tao_bo_dem()
print(bo_dem(), bo_dem(), bo_dem())
code
1 2 3

Đây là closure — hàm trong giữ biến của hàm ngoài. Chương này chỉ cần nhận diện; phạm vi biến sẽ giải thích kỹ ở bài 4.

Thực hành

  1. Viết hàm chao(name, greeting="Xin chào") và gọi với một, hai tham số.
  2. In help(chao) hoặc chao.__doc__ để xem docstring.
  3. Thêm type hint cho hàm và thử truyền sai kiểu để thấy Python không chặn.
  4. Gọi hàm trước khi định nghĩa và đọc NameError.
  5. Tách đoạn tính điểm trung bình thành hàm, xử lý list rỗng.
  6. Đổi tên hàm theo động từ + đối tượng (tinh_diem_trung_binh) và nhận xét độ rõ.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
In ra <function ...>Gọi thiếu ngoặcThêm ()
NameError khi gọiHàm định nghĩa sau lời gọiĐưa def lên trước
IndentationErrorThân hàm không thụt lềThụt 4 dấu cách
SyntaxError ở defThiếu : hoặc sai tênKiểm tra chữ ký
Hàm không trả gìQuên returnThêm return
Type hint không báo lỗiHint không kiểm tra lúc chạyDùng mypy/pyright nếu cần
Docstring không hiệnĐặt không phải câu đầu thânĐưa lên dòng đầu sau def

Tóm tắt

  • def ten_ham(tham_so): + thân thụt lề; gọi bằng ten_ham(...).
  • Docstring là tài liệu sống; type hint hỗ trợ editor, không kiểm tra lúc chạy.
  • Định nghĩa trước khi gọi; đặt phần chạy trong if __name__ == "__main__".
  • Tách hàm khi có logic lặp lại, cần tên, hoặc cần kiểm thử riêng.
  • Tên hàm nên là động từ mô tả việc nó làm.

Use ← → to move between lessons