فصل ۳: کنترل جریان — تصمیم‌گیری و تکرار

match/case: تطبیق الگوی ساختاری

فراتر از switch

از Python 3.10 دستور match/case به زبان اضافه شد. در نگاه اول شبیه switch در زبان‌های دیگر است، اما در واقع یک ابزار تطبیق الگوی ساختاری است: نه فقط مقدار، که شکل داده را هم بررسی می‌کند و اجزای آن را در متغیرها می‌ریزد.

ساده‌ترین شکل: تطبیق مقدار

status = "shipped"

match status:
    case "new":
        text = "ثبت شده"
    case "weaving" | "finishing":        # چند مقدار با |
        text = "در حال تولید"
    case "shipped":
        text = "ارسال شد"
    case _:                              # حالت پیش‌فرض
        text = "نامشخص"

برخلاف C، هیچ «سقوط» (fall-through) به case بعدی وجود ندارد و نیازی به break نیست. case _ مثل else عمل می‌کند و همیشه آخر می‌آید.

قدرت واقعی: تطبیق ساختار

فرض کنید برنامه‌ای خط فرمان دارید و کاربر دستورهایی مثل add رضایی 3 تایپ می‌کند. با تطبیق روی لیست کلمات، هم دستور و هم آرگومان‌ها را در یک قدم جدا می‌کنید:

def handle(command: str) -> str:
    match command.split():
        case ["add", name, qty] if qty.isdigit():
            return f"ثبت {qty} تخته برای {name}"
        case ["add", *_]:
            return "فرمت: add نام تعداد"
        case ["list"] | ["ls"]:
            return "فهرست سفارش‌ها"
        case ["quit" | "exit"]:
            return "خداحافظ"
        case []:
            return "چیزی ننوشتید"
        case _:
            return f"دستور ناشناخته: {command}"

print(handle("add رضایی 3"))   # ثبت 3 تخته برای رضایی
print(handle("add x"))         # فرمت: add نام تعداد

اجزای این مثال: name و qty متغیر گیرنده (capture) هستند و هر مقداری را می‌گیرند؛ if qty.isdigit() یک guard است که شرط اضافه می‌گذارد؛ *_ «هر تعداد عضو باقی‌مانده» را می‌پذیرد؛ و الگوی لیست فقط وقتی تطبیق می‌خورد که تعداد اعضا هم جور باشد.

تطبیق دیکشنری (مثلاً داده‌ی JSON)

def describe(event: dict) -> str:
    match event:
        case {"type": "order", "qty": int(q)} if q > 10:
            return f"سفارش عمده: {q} تخته"
        case {"type": "order", "qty": q}:
            return f"سفارش: {q}"
        case {"type": "cancel", "id": order_id}:
            return f"لغو سفارش {order_id}"
        case _:
            return "رویداد ناشناخته"

الگوی دیکشنری فقط کلیدهای ذکرشده را بررسی می‌کند و کلیدهای اضافه را نادیده می‌گیرد. int(q) یعنی «مقدار باید از نوع int باشد و در q قرار گیرد».

کی از match استفاده نکنیم؟

اگر فقط دو سه مقدار ساده را مقایسه می‌کنید، if/elif کوتاه‌تر و آشناتر است. match وقتی می‌درخشد که داده ساختار دارد: دستورهای چندکلمه‌ای، پیام‌های JSON، تاپل مختصات یا درخت‌ها.

نکته‌هایی که کمتر کسی می‌داند

  • دام بزرگ: case QUIT: که QUIT یک متغیر ساده باشد، مقایسه نمی‌کند بلکه هر چیزی را می‌گیرد و در QUIT می‌ریزد! ثابت‌ها باید «نقطه‌دار» باشند، مثل case Status.QUIT: یا case config.QUIT:.
  • الگوی رشته‌ای "abc" با لیست ["a","b","c"] تطبیق نمی‌خورد؛ match عمداً رشته را «دنباله» حساب نمی‌کند تا غافلگیری پیش نیاید.
  • با as می‌توانید کل زیرالگو را هم نگه دارید: case ["add" | "new" as cmd, name]:.
  • برای گرفتن کلیدهای اضافه‌ی دیکشنری از **rest استفاده کنید: case {"type": "order", **rest}:.
  • متغیرهای capture بعد از match هم باقی می‌مانند؛ حتی در caseی که در نهایت به‌خاطر guard رد شده، ممکن است مقدار گرفته باشند.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.