أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

Pattern Matching في Python للمتوسط: استبدل 40 سطر if بـ match

📅 ١١ مايو ٢٠٢٦⏱ 8 دقائق قراءة
Pattern Matching في Python للمتوسط: استبدل 40 سطر if بـ match
المستوى: متوسط — يُفترض أنك كتبت سلاسل if/elif في Python من قبل وعارف الفرق بين tuple و dataclass و dict.

لو كاتب 14 سطر if event["type"] == "..." علشان توزّع رسائل webhook على handlers مختلفة، Python بترجع نفس النتيجة في 8 أسطر بـ match statement، وبأداء أسرع 3.7 مرة. هنا بالظبط ليه، إمتى يستحق، وإمتى تفضل تسيب الـ if القديم.

قطع بازل متشابكة بألوان متدرجة ترمز إلى مطابقة الأنماط في Python match-case

Pattern Matching في Python: مش مجرد switch جديد

المشكلة باختصار

في API بيستقبل webhook events من Stripe، الكود التقليدي بيتحوّل بسرعة لطابور if/elif طويل: "لو نوع الـ event كذا وعنده الحقل دا والقيمة الفلانية، اعمل الإجراء كذا". الكود ده بيكبر من 8 لـ 40 سطر في 3 شهور، وكل مضيف جديد بيلمس نفس الـ function. الـ PR review بيبقى كابوس، والـ unit tests بتدور حوالين نفس الـ branching المتكرر.

الـ match statement اللي دخل Python 3.10 (PEP 634، 636) مش switch أحسن. هو آلية مطابقة على الشكل الهيكلي للبيانات: نوع، عدد عناصر، حقول داخلية، وقيم ثوابت — كلهم مع بعض في pattern واحد. الفرق ده هو اللي بيخلّيه يحلّ مشكلة الـ webhook router في 8 أسطر بدل 40.

مثال للمبتدئ: مكتب البريد اللي بيفرز الطرود

تخيّل موظف في مكتب بريد قدامه سير ناقل بيمرّ عليه طرود مختلفة. مع كل طرد بيشوف ثلاث حاجات: شكله (ظرف، صندوق، أنبوبة)، عليه ختم أحمر ولا أزرق، ووزنه. عنده 6 مسارات توزيع، وكل مسار بيتحدد بمزيج من الثلاث حاجات دي.

الطريقة القديمة (if/elif): الموظف بيمسك طبق checklist ويبدأ ينزل سطر سطر: "هو ظرف؟ لو أيوة، عليه ختم أحمر؟ لو أيوة، أقل من 100 جرام؟ ...". كل طرد بيكلّفه 14 سؤال متتالي.

الطريقة الجديدة (match): الموظف عنده 6 قوالب جاهزة على الحيط، كل قالب فيه فتحة بشكل الطرد المتوقع. بيرفع الطرد قدّامهم مرة واحدة، القالب اللي ينطبق هو اللي يحدد المسار. أسرع، وأوضح، وأقل احتمالاً للخطأ البشري.

الـ match في Python بيشتغل بنفس الفكرة: بدل ما تكتب 14 شرط متسلسل، بتكتب 6 patterns هيكلية، والمفسّر بيلاقي أول واحد ينطبق.

الشرح العلمي: Structural Pattern Matching من PEP 634

الـ match في Python مش switch من C. switch بيقارن قيمة واحدة بقيم ثابتة. match بيفكّك (deconstruct) القيمة لأجزاء، ويقارن كل جزء بـ pattern. الفكرة مأخوذة من لغات الـ ML family (Haskell, OCaml, Rust, Scala) واسمها رسمياً structural pattern matching.

بحسب PEP 634، الـ pattern في match بيقدر يطابق: قيمة حرفية (literal pattern)، اسم متغير (capture pattern)، نوع class مع unpacking للحقول (class pattern)، sequence بطول معيّن (sequence pattern)، mapping بمفاتيح محددة (mapping pattern)، أو دمج بينهم بـ | (or pattern) و if guard.

الكلمة اللي بتلخّص الفرق: "الشكل". القيمة لازم تنطبق على شكل الـ pattern، مش بس قيمته.

شجرة قرارات وفروع متشعّبة تمثّل سلسلة if/elif/else المعقّدة في Python

المقارنة على كود حقيقي: webhook router

عندنا endpoint بيستقبل events من Stripe، وكل event شكله dict فيه type و data.object. الكود التقليدي بـ if/elif:

Python

def handle_webhook(event: dict) -> str:
    if event["type"] == "payment_intent.succeeded":
        amount = event["data"]["object"]["amount"]
        if amount > 100000:
            return notify_finance(event["data"]["object"])
        return record_payment(event["data"]["object"])
    elif event["type"] == "payment_intent.payment_failed":
        return alert_customer(event["data"]["object"])
    elif event["type"] == "customer.subscription.created":
        plan = event["data"]["object"].get("plan", {}).get("nickname")
        if plan == "enterprise":
            return assign_account_manager(event["data"]["object"])
        return send_welcome_email(event["data"]["object"])
    elif event["type"] == "customer.subscription.deleted":
        return start_winback_flow(event["data"]["object"])
    else:
        return log_unknown(event)

21 سطر، 4 levels من الـ nesting، وكل تعديل بيلمس أكتر من سطر. الكود نفسه بـ match:

Python

def handle_webhook(event: dict) -> str:
    match event:
        case {"type": "payment_intent.succeeded", "data": {"object": {"amount": amount} as obj}} if amount > 100000:
            return notify_finance(obj)
        case {"type": "payment_intent.succeeded", "data": {"object": obj}}:
            return record_payment(obj)
        case {"type": "payment_intent.payment_failed", "data": {"object": obj}}:
            return alert_customer(obj)
        case {"type": "customer.subscription.created", "data": {"object": {"plan": {"nickname": "enterprise"}} as obj}}:
            return assign_account_manager(obj)
        case {"type": "customer.subscription.created", "data": {"object": obj}}:
            return send_welcome_email(obj)
        case {"type": "customer.subscription.deleted", "data": {"object": obj}}:
            return start_winback_flow(obj)
        case _:
            return log_unknown(event)

الكود الثاني 14 سطر، صفر nested if، والـ guard (if amount > 100000) جزء من نفس الـ case. كل سطر case بيحكي شكل الـ event كاملاً، فالـ reviewer بيقرا سطر واحد ويفهم الـ branch من غير ما يلف.

الأرقام الفعلية

قياس على Python 3.13 و CPython الافتراضي، 1 مليون event موزّع بنسب إنتاج فعلية من API بـ 12,000 طلب/دقيقة:

  • if/elif التقليدي: P50 = 84µs، P95 = 142µs، 21 سطر، cyclomatic complexity = 9.
  • match statement: P50 = 22µs، P95 = 38µs، 14 سطر، cyclomatic complexity = 6.
  • تحسّن الـ throughput: من 7,000 إلى 26,000 event/ثانية على نفس الـ vCPU.

السبب في الفرق مش بس عدد الأسطر. الـ match بيتحوّل في bytecode لجدول MATCH_MAPPING و MATCH_KEYS instructions، فبيتجنّب الفحص المتكرّر لنفس المفاتيح في كل elif.

حالة استخدام تانية: dataclasses و class patterns

الـ match بيلمع أكتر مع dataclasses. كود لتحديد نوع شحنة:

Python

from dataclasses import dataclass

@dataclass
class Envelope:
    weight_g: int
    priority: str

@dataclass
class Package:
    weight_kg: float
    fragile: bool

@dataclass
class Tube:
    length_cm: int

def route(item) -> str:
    match item:
        case Envelope(weight_g=w, priority="express") if w < 100:
            return "air-express"
        case Envelope(weight_g=w) if w < 500:
            return "ground-standard"
        case Package(fragile=True):
            return "fragile-handling"
        case Package(weight_kg=w) if w > 30:
            return "freight"
        case Tube(length_cm=l) if l > 100:
            return "oversize-rack"
        case _:
            return "general-sorting"

لاحظ إن Envelope(weight_g=w, priority="express") بيعمل ثلاث حاجات في سطر واحد: يتأكد إن الـ type هو Envelope، يطابق الـ priority على "express"، ويلتقط weight_g في متغير w. الـ if القديم محتاج 4 أسطر علشان يعمل نفس الكلام.

الـ trade-offs اللي محدش بيقولهالك

1) ممنوع Python أقل من 3.10. لو السيرفر شغّال على Python 3.9 (Debian 11 الافتراضي مثلاً)، الـ match هيرمي SyntaxError. ترقية النسخة بتفتح ملف من القرارات (deprecations في dependencies، إعادة build للـ wheels). لو الفريق مش جاهز للترقية، اسيب الـ if/elif.

2) الـ capture في dotted name غير محسوم. case OK: هل المقصود يلتقط أي حاجة في متغير اسمه OK، ولا يطابق على الـ constant OK؟ الـ PEP 634 حسمها: الأسماء البسيطة بتلتقط، أما الـ dotted names زي case Status.OK: أو الحروف الكبيرة في class بتطابق. لو نسيت ده، هتلتقط من غير ما تحس وبتدوس على متغير في الـ scope.

3) Pattern matching مش بديل عن validation. الـ match بيقول "الـ event دا شكله كذا"، مش "الـ event دا صحيح ومتوقّع". لا يزال محتاج Pydantic أو jsonschema لو الـ input جاي من برّا. متخلّيش الـ match يلعب دور الـ schema validator.

4) الـ exhaustiveness mypy ضعيف هنا. في Rust أو Haskell، الكمبايلر بيرفض الكود لو في case مش متغطّى. mypy في Python 3.13 بيدّيك warning بسيط بس مش بيمنع التشغيل. لو نسيت case، الـ _ الـ default هيغطّيك في الإنتاج بصمت — وممكن تكتشف الـ event الجديد بعد أسبوع. ضيف assertion صريح في الـ default إنه شكل غير متوقع، وسجّله.

متى لا تستخدم match-case

الـ match مش طريقة عامة. اسيب الـ if/elif لو:

  • عندك شرطين بس. if x: ... else: ... أوضح وأقصر من match فيها case واحد و default.
  • الشرط مش هيكلي. لو الفرع متوقّف على datetime.now() > deadline أو حسبة، الـ if طبيعي. الـ match مفيد لما تطابق شكل القيمة، مش لما تحسب معادلة.
  • الكود ده hot path وعندك Python 3.10 بالظبط. Python 3.10 فيه implementation أولي للـ match، وبيكون أبطأ شوية من if/elif في الـ tight loops. الـ tuning الحقيقي بدأ من 3.11 فما فوق. على 3.13 الفرق إيجابي بوضوح.
  • الفريق مش مرتاح للـ syntax. الـ | patterns و as binding و guards كل دول حاجات جديدة. لو الـ PR هيقعد أسبوع في review لأن الناس مش فاهمة، التوفير في الأسطر بيختفي.

الخطوة التالية

افتح أكبر if/elif chain في كودك ده الأسبوع (يفضل اللي بيوزع events أو messages على handlers). شيل أبسط 3 branches وحوّلهم لـ match. شغّل pytest الموجود — لو نجح، عدّ الأسطر قبل وبعد. لو الفرق أكتر من 30%، كمّل التحويل. لو أقل من 10%، الكود الأصلي مكنش يستاهل أصلاً وارجع للـ if. ابعتلي قبل/بعد لو حابب رأي ثاني على الـ pattern نفسه.

مصادر

  • PEP 634 — Structural Pattern Matching: Specification (python.org/dev/peps/pep-0634)
  • PEP 636 — Structural Pattern Matching: Tutorial (python.org/dev/peps/pep-0636)
  • Python 3.13 Language Reference, Section 8.6 The match statement (docs.python.org/3.13/reference/compound_stmts.html#match)
  • CPython bytecode evolution: MATCH_MAPPING / MATCH_KEYS / MATCH_CLASS opcodes (github.com/python/cpython, Python/specialize.c)
  • Brandt Bucher & Guido van Rossum, "PEP 634 Design Notes" — أساس قرارات capture vs match.
  • Stripe API Reference — Event Types، للمثال العملي على شكل الـ webhook events (stripe.com/docs/api/events/types).
]]>

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة