← Lập trình Python cơ bản

Bài 6 · Vận dụng · 22 phút· Cập nhật 11/06/2026

Hàm & phạm vi trong Python

Biên soạn bởi Nguyễn Anh Tuấn

Hàm trong Python: def, tham số mặc định, *args/**kwargs, phạm vi biến & quy tắc LEGB, bẫy mutable default - hàm là object hạng nhất (first-class).

Hàm gom một việc lặp lại thành một khối có tên, nhận tham sốreturn kết quả. Định nghĩa bằng def:

ham.py

def chao(ten):
    return f"Xin chao, {ten}!"

def cong(a, b):
    return a + b

print(chao("Lan"))
print(cong(3, 5))

Kết quả khi chạy

Xin chao, Lan!
8
  • def ten(tham_số): rồi thân hàm thụt vào; return trả giá trị.
  • Không viết return → hàm trả về None.
  • Gọi hàm: ten(đối_số). Đối số khớp tham số theo vị trí (hoặc theo tên).

Tham số có thể có giá trị mặc định; *args gom các đối số vị trí dư thành tuple; **kwargs gom các đối số từ khoá dư thành dict:

thamso.py

def f(a, b=2, *args, **kwargs):
    print(a, b, args, kwargs)

f(1)                 # b dung mac dinh
f(1, 3, 4, 5, x=9)   # b=3; args=(4,5); kwargs={'x':9}

Kết quả khi chạy

1 2 () {}
1 3 (4, 5) {'x': 9}
  • Mặc định: def f(b=2) - dùng khi không truyền b.
  • *args: gom đối số VỊ TRÍ dư → tuple; **kwargs: gom đối số TỪ KHOÁ dư → dict.
  • Gọi theo tên (keyword) cho rõ: ve(rong=5, cao=3) - khỏi nhớ thứ tự.

Khi gặp một tên, Python tìm theo thứ tự LEGB: Local (trong hàm) → Enclosing (hàm bao ngoài) → Global (module) → Built-in. Bấm thử từng tên để xem Python tìm ở đâu:

x = "global-x" # Global
def ngoai(): # Enclosing
y = "enclosing-y"
def trong(): # Local
z = "local-z"
print(z, y, x, len("ab"))
trong()
Trong trong(), tra cứu tên:
Local - trong() chứa: z ✓ tìm thấy “z” ở đây
Enclosing - ngoai() chứa: y
Global - module chứa: xngoai
Built-in chứa: lenprintrangesum
Python duyệt Local → Enclosing → Global → Built-in và dừng ở Local - nơi đầu tiên có “z”.

global & nonlocal

Mặc định, gán một tên trong hàm tạo biến local. Muốn gán đè biến cấp module dùng global ten; gán đè biến ở hàm bao ngoài dùng nonlocal ten. Dùng tiết kiệm - đọc/truyền tham số & return thường sạch hơn là sửa biến global.

Giá trị mặc định được tạo MỘT LẦN lúc định nghĩa hàm, rồi dùng chung cho mọi lần gọi. Nếu mặc định là list (mutable), nó sẽ “nhớ” dữ liệu cũ:

bay.py - SAI

def them(x, gio=[]):     # BAY: list mac dinh dung chung
    gio.append(x)
    return gio

print(them(1))   # [1]
print(them(2))   # [1, 2]  <- van con 1!

Kết quả khi chạy

[1]
[1, 2]

sua.py - ĐÚNG

def them(x, gio=None):
    if gio is None:      # tao list MOI moi lan goi
        gio = []
    gio.append(x)
    return gio

print(them(1))   # [1]
print(them(2))   # [2]

Kết quả khi chạy

[1]
[2]
  • ĐỪNG dùng list/dict/set làm giá trị mặc định.
  • Dùng None làm mặc định, rồi tạo object mới bên trong hàm.
  • Object immutable (int, str, None) làm mặc định thì an toàn.

Như bài “mọi thứ là object”: hàm cũng là object. Bạn gán nó cho biến, bỏ vào list, hay truyền làm đối số cho hàm khác:

hangnhat.py

def binh_phuong(n):
    return n * n

def ap_dung(ham, gt):    # nhan MOT HAM lam doi so
    return ham(gt)

print(ap_dung(binh_phuong, 5))   # truyen ham binh_phuong

Kết quả khi chạy

25

Vì hàm truyền được, Python có sẵn hàm bậc cao (higher-order) nhận hàm làm đối số: map (biến đổi từng phần tử), filter (lọc), và reduce (gộp dần về một giá trị). Kèm lambda (hàm ẩn danh một dòng) rất gọn:

map / filter / reduce + lambda

from functools import reduce

so = [1, 2, 3, 4, 5]

binh_phuong = list(map(lambda n: n * n, so))          # [1, 4, 9, 16, 25]
chan        = list(filter(lambda n: n % 2 == 0, so))  # [2, 4]
tong        = reduce(lambda a, b: a + b, so)          # 15

print(binh_phuong)
print(chan)
print(tong)

Kết quả khi chạy

[1, 4, 9, 16, 25]
[2, 4]
15

Pythonic: thường nên dùng comprehension

map/filter chạy tốt, nhưng nhiều người Python thấy list comprehension dễ đọc hơn: [n*n for n in so] thay map, [n for n in so if n % 2 == 0] thay filter. Dùng map/filter khi đã có sẵn hàm để truyền; dùng reduce có chừng mực (vòng lặp / sum / math.prod thường rõ hơn).

Sắp tới

Việc “hàm là object, truyền/trả được” là nền của decorator và lập trình hàm - học sâu ở khoá Python nâng cao.

Câu hỏi thường gặp

Trả về None. Trong Python mọi hàm đều trả về một giá trị; nếu bạn không viết return (hoặc viết return trống), giá trị đó là None. Vì vậy x = ham_in_ra(...) có thể vô tình bằng None.

*args gom các đối số VỊ TRÍ dư thành một tuple; **kwargs gom các đối số TỪ KHOÁ (key=value) dư thành một dict. Vd def f(a, *args, **kwargs): gọi f(1, 2, 3, x=9) thì a=1, args=(2, 3), kwargs={"x": 9}. Tên args/kwargs chỉ là quy ước; dấu *** mới quan trọng.

Là thứ tự Python tìm một TÊN: Local (trong hàm hiện tại) → Enclosing (hàm bao ngoài) → Global (cấp module) → Built-in (len, print…). Tìm thấy ở đâu thì dùng ở đó và dừng; không thấy ở cả 4 nơi thì NameError.

Mặc định, GÁN một tên bên trong hàm tạo biến LOCAL. Muốn gán đè biến ở cấp module, khai báo global ten; muốn gán đè biến ở hàm bao ngoài (enclosing), dùng nonlocal ten. Dùng tiết kiệm - lạm dụng global làm code khó dò.

Vì giá trị mặc định được tạo MỘT LẦN lúc định nghĩa hàm, rồi DÙNG CHUNG cho mọi lần gọi. Nếu mặc định là list/dict (mutable) và bạn sửa nó, lần gọi sau thấy dữ liệu cũ. Cách đúng: để mặc định None rồi tạo list mới bên trong (xem Bước 4).

Nghĩa là hàm cũng là một OBJECT như int hay str (nhớ bài “mọi thứ là object”): gán cho biến, bỏ vào list, truyền làm đối số, hay trả về từ hàm khác - đều được. Đây là nền của decorator và lập trình hàm sẽ học ở khoá nâng cao.

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Một hàm không có lệnh return thì trả về gì?

  1. 1

    Hàm đầu tiên

    Viết hàm dien_tich(dai, rong) trả về diện tích hình chữ nhật; in dien_tich(4, 5).

    Hoàn thành khi: def dien_tich(dai, rong): return dai * rong → 20.

  2. 2

    Tham số mặc định

    Viết hàm chao(ten, loi="Xin chao") in "Xin chao, <ten>!". Gọi cả có và không truyền loi.

    Hoàn thành khi: chao("Lan")chao("Nam", "Hi") đều chạy; mặc định dùng khi không truyền.

  3. 3

    Tổng tuỳ ý

    Viết hàm tong(*so) cộng mọi đối số. Gọi tong(1, 2, 3, 4).

    Hoàn thành khi: def tong(*so): return sum(so) → 10. *so gom thành tuple.

  4. 4

    LEGB

    Dùng công cụ Bước 3: tra cứu z, y, x, len, khong_co. Mỗi tên tìm thấy ở scope nào?

    Hoàn thành khi: z→Local, y→Enclosing, x→Global, len→Built-in, khong_coNameError.

  5. 5

    Sửa bẫy mặc định

    Sửa def them(x, gio=[]) để mỗi lần gọi không “nhớ” list cũ.

    Hoàn thành khi: def them(x, gio=None): if gio is None: gio = []; gio.append(x); return gio.

  6. 6

    Hàm first-class

    Viết ap_dung(ham, gt) trả về ham(gt). Gọi ap_dung với một hàm bình phương và 6.

    Hoàn thành khi: ap_dung(lambda n: n*n, 6) hoặc truyền hàm đã def → 36.