DHDataHọc
Lý thuyếtBài 11/306 phút đọc+10 XP

match / case

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

  • Cú pháp match / case từ Python 3.10.
  • So khớp theo giá trị, theo kiểu, theo cấu trúc list/dict.
  • Guard if trong case và wildcard _.
  • Vì sao tên trần trong case luôn khớp — cái bẫy lớn nhất.
  • Khi nào dùng match, khi nào if/elif vẫ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:

code
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"
code
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))
code
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ẫuKhớ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ộ:

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

code
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)))
code
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:

code
match [1, 2, 3, 4]:
    case [first, *rest]:
        print(first, rest)
code
1 [2, 3, 4]

Mẫu lớp kiểm tra kiểu và trích thuộc tính:

code
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})")
code
(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:

code
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]))
code
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ố:

code
status = "active"

match "inactive":
    case status:
        print("khớp")
code
khớp

Nhá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:

code
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")
code
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êm case _ 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ốngNê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ểumatch
Cần guard phức tạp trên nhiều biếnif/elif
Handler sự kiện theo typematch với mẫu dict
Chạy trên Python 3.9 trở xuốngif/elif

Thực hành

  1. Viết phan_loai như ví dụ đầu và thử với 0, 42, -7, "", "An", [1, 2, 3], 3.14.
  2. Viết xu_ly cho ba loại sự kiện click/view/scroll.
  3. Viết toa_do cho bốn trường hợp trên trục và gốc.
  4. Cố ý viết case status: với biến bên ngoài và giải thích vì sao luôn khớp.
  5. Đổi hàm phân loại sang if/elif và so sánh độ dài, độ rõ.
  6. Thêm case _: vào một match thiếu nhánh mặc định.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
SyntaxError: invalid syntax ở matchPython < 3.10Nâng lên 3.10+ hoặc dùng if/elif
Mọi giá trị rơi vào một nhánhcase 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 đúngNhánh trước quá rộngSắp từ cụ thể đến tổng quát
NameError sau matchBiến bắt chỉ sống trong nhánhGá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/case so khớp cấu trúc, không rơi nhánh, không cần break.
  • Mẫu dict chỉ cần các khóa được nêu; **info gom phần còn lại.
  • Guard if lọ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.

Dùng ← → để chuyển bài