match / case
Bạn sẽ học được
- Cú pháp
match/casetừ Python 3.10. - So khớp theo giá trị, theo kiểu, theo cấu trúc list/dict.
- Guard
iftrongcasevà wildcard_. - Vì sao tên trần trong
caseluôn khớp — cái bẫy lớn nhất. - Khi nào dùng
match, khi nàoif/elifvẫn tốt hơn.
Khái niệm cốt lõi
match là so khớp cấu trúc (structural pattern matching), không chỉ là switch của ngôn ngữ khác. Đối tượng được so với từng mẫu (pattern) từ trên xuống; nhánh đầu tiên khớp sẽ chạy và không rơi xuống nhánh sau:
def phan_loai(value):
match value:
case 0:
return "không"
case int(n) if n > 0:
return f"dương {n}"
case int(n):
return f"âm {n}"
case str(s) if s.strip() == "":
return "chuỗi rỗng"
case str(s):
return f"chuỗi {s}"
case [first, *rest]:
return f"list bắt đầu {first}, còn {len(rest)}"
case {"type": "click", **info}:
return f"click {info}"
case _:
return "khác"print(phan_loai(0), "|", phan_loai(42), "|", phan_loai(-7))
print(phan_loai(""), "|", phan_loai("An"))
print(phan_loai([1, 2, 3]), "|", phan_loai({"type": "click", "x": 10}))
print(phan_loai(3.14))không | dương 42 | âm -7
chuỗi rỗng | chuỗi An
list bắt đầu 1, còn 2 | click {'x': 10}
khácĐọc từng mẫu:
| Mẫu | Khớp khi |
|---|---|
case 0: | Giá trị bằng 0 |
case int(n) if n > 0: | Là int, gán vào n, và guard đúng |
case int(n): | Là int (đã trừ số dương ở trên) |
case str(s) if s.strip() == "": | Là str và rỗng sau khi cắt khoảng trắng |
case [first, *rest]: | Là list có ít nhất một phần tử |
case {"type": "click", **info}: | Là dict có "type" == "click" |
case _: | Luôn khớp — nhánh mặc định |
Lưu ý: bool là con của int, nên True sẽ khớp case int(n) và cho n = 1. Nếu cần phân biệt, đặt case True / case False trước case int(...).
So khớp cấu trúc dict
Mẫu dict chỉ kiểm tra các khóa được nêu, không cần khớp toàn bộ:
def xu_ly(event):
match event:
case {"type": "click", "button": "left", "x": x, "y": y}:
return f"click trái tại ({x}, {y})"
case {"type": "click", "button": button}:
return f"click {button}"
case {"type": "view"}:
return "view"
case _:
return "không rõ"
print(xu_ly({"type": "click", "button": "left", "x": 10, "y": 20}))
print(xu_ly({"type": "click", "button": "right"}))
print(xu_ly({"type": "view"}))
print(xu_ly({"type": "scroll"}))click trái tại (10, 20)
click right
view
không rõNhánh đầu khớp vì dict có đủ bốn khóa với giá trị đúng. Nhánh hai khớp vì vẫn là click nhưng thiếu "x"/"y" hoặc "button" không phải "left" — mẫu chỉ cần ba khóa nêu ra. Đây là cách viết handler sự kiện rất gọn: mỗi nhánh mô tả hình dạng dữ liệu nó chấp nhận.
Mẫu dãy và mẫu lớp
def toa_do(point):
match point:
case (0, 0):
return "gốc toạ độ"
case (x, 0):
return f"trên trục hoành tại {x}"
case (0, y):
return f"trên trục tung tại {y}"
case (x, y):
return f"điểm ({x}, {y})"
print(toa_do((0, 0)))
print(toa_do((5, 0)))
print(toa_do((0, -3)))
print(toa_do((3, 4)))gốc toạ độ
trên trục hoành tại 5
trên trục tung tại -3
điểm (3, 4)Mẫu dãy dùng [] cho list, () cho tuple và cũng khớp được list. *rest gom phần còn lại:
match [1, 2, 3, 4]:
case [first, *rest]:
print(first, rest)1 [2, 3, 4]Mẫu lớp kiểm tra kiểu và trích thuộc tính:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
match Point(3, 4):
case Point(x=0, y=0):
print("gốc")
case Point(x=x, y=y):
print(f"({x}, {y})")(3, 4)Guard và wildcard
if sau mẫu là guard: mẫu phải khớp trước, rồi guard mới được kiểm tra. Nếu guard sai, Python thử nhánh tiếp theo — biến đã gán trong mẫu không rò ra ngoài nhánh thất bại.
case _: là wildcard, luôn khớp, không gán gì. Dùng làm nhánh mặc định thay cho else. Có thể kết hợp case None: hoặc case str() | bytes(): để khớp một trong nhiều kiểu:
def mo_ta(value):
match value:
case int() | float():
return "số"
case str() | bytes():
return "chuỗi"
case _:
return "khác"
print(mo_ta(3), mo_ta(3.5), mo_ta("An"), mo_ta([1]))số chuỗi khác| trong mẫu là "hoặc", không phải bitwise OR.
Cái bẫy: tên trần luôn khớp
Trong mẫu, một tên không có dấu chấm là biến bắt giá trị, không phải hằng số:
status = "active"
match "inactive":
case status:
print("khớp")khớpNhánh này luôn chạy và ghi đè status thành "inactive". Muốn so với giá trị của biến, phải dùng dấu chấm — hằng số, enum, hoặc thuộc tính:
from enum import Enum
class Status(Enum):
ACTIVE = "active"
INACTIVE = "inactive"
match Status.INACTIVE:
case Status.ACTIVE:
print("đang hoạt động")
case Status.INACTIVE:
print("ngừng hoạt động")ngừng hoạt độngĐây là lý do các hướng dẫn khuyên dùng Enum thay cho chuỗi khi có match. Với chuỗi thường, if/elif an toàn hơn vì == không bao giờ biến thành phép gán.
Ba lỗi cấu trúc khác cần nhớ:
- Không có
break: mỗi nhánh tự kết thúc, không rơi xuống. - Không có nhánh nào khớp và không có
case _:→ không làm gì, không báo lỗi. Luôn thêmcase _nếu muốn phát hiện dữ liệu lạ. - Biến bắt trong mẫu chỉ tồn tại trong nhánh đó; dùng ngoài sẽ
NameError.
Khi nào dùng match
match mạnh khi hình dạng dữ liệu quyết định cách xử lý: sự kiện JSON, cây cú pháp, lệnh CLI, kết quả API nhiều kiểu. Nó yếu khi bạn chỉ so sánh một biến với vài giá trị — if/elif ngắn hơn và không cần Python 3.10.
| Tình huống | Nên dùng |
|---|---|
| So một biến với 2–3 giá trị | if/elif |
| Dữ liệu lồng nhau, nhiều kiểu | match |
| Cần guard phức tạp trên nhiều biến | if/elif |
Handler sự kiện theo type | match với mẫu dict |
| Chạy trên Python 3.9 trở xuống | if/elif |
Thực hành
- Viết
phan_loainhư ví dụ đầu và thử với0,42,-7,"","An",[1, 2, 3],3.14. - Viết
xu_lycho ba loại sự kiện click/view/scroll. - Viết
toa_docho bốn trường hợp trên trục và gốc. - Cố ý viết
case status:với biến bên ngoài và giải thích vì sao luôn khớp. - Đổi hàm phân loại sang
if/elifvà so sánh độ dài, độ rõ. - Thêm
case _:vào mộtmatchthiếu nhánh mặc định.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
SyntaxError: invalid syntax ở match | Python < 3.10 | Nâng lên 3.10+ hoặc dùng if/elif |
| Mọi giá trị rơi vào một nhánh | case ten_bien: bắt mọi thứ | Dùng hằng/Enum hoặc == |
| Biến ngoài bị đổi giá trị | Tên trần trong mẫu là phép gán | Đổi sang Enum/thuộc tính |
| Nhánh không chạy dù dữ liệu đúng | Nhánh trước quá rộng | Sắp từ cụ thể đến tổng quát |
NameError sau match | Biến bắt chỉ sống trong nhánh | Gán kết quả ra biến ngoài |
| Không có gì xảy ra với dữ liệu lạ | Thiếu case _ | Thêm nhánh mặc định |
True khớp case int(n) | bool là con của int | Đặt case True/False trước |
Tóm tắt
match/caseso khớp cấu trúc, không rơi nhánh, không cầnbreak.- Mẫu dict chỉ cần các khóa được nêu;
**infogom phần còn lại. - Guard
iflọc thêm sau khi mẫu khớp. - Tên trần trong mẫu là phép gán — dùng Enum khi cần so với hằng.
- Thiếu
case _thì dữ liệu lạ bị bỏ qua im lặng. - Chỉ dùng khi hình dạng dữ liệu phức tạp; so sánh đơn giản vẫn nên là
if/elif.
Use ← → to move between lessons