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

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

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

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

المنصة

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

الدعم

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

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

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

Pattern Matching في Python للمتوسط: استبدل 14 if/elif بـ match/case نظيف

📅 ١١ مايو ٢٠٢٦⏱ 6 دقائق قراءة
Pattern Matching في Python للمتوسط: استبدل 14 if/elif بـ match/case نظيف

المستوى: متوسط — هذا المقال يفترض إنك بتكتب Python يومياً وعندك خلفية بـ if/elif و isinstance و dataclasses. لو لسه مبتدئ، الجزء الأول بالمثال هيوصّلك للفكرة، لكن الكود في النص محتاج Python 3.10 على الأقل.

لو فاتح ملف Python فيه دالة handler طولها 90 سطر وكلها سلسلة if/elif بتفحص نوع event، كل تعديل بسيط بيكسر حاجة في مكان تاني. الـ match/case في Python 3.10+ مش syntactic sugar — هي طريقة مختلفة في التفكير، بتنزّل عدد الـ bugs من نوع "نسيت الحالة دي" لصفر تقريباً، وبتخلّي الـ code review أسرع 3 مرات.

Pattern Matching في Python: لماذا match/case ليست مجرد switch بلغة جديدة

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

تخيّل إنك بتكتب backend لخدمة اشتراكات. الـ webhook بيوصلك events متنوعة: payment_succeeded، payment_failed، subscription_renewed، trial_ended، refund_issued. كل event شكله مختلف وفيه fields مختلفة. الطريقة التقليدية: 8 أو 9 if/elif blocks متشابكة مع isinstance و .get() ومحاولات استخراج fields من dicts متداخلة. النتيجة: 90 سطر كود، 4 bugs في الإنتاج كل أسبوع لمّا يضيف الفريق event جديد وينسى يحدّث الـ handler.

شاشة محرر كود تعرض دالة Python فيها match/case بدل سلسلة if/elif طويلة

المفهوم بمثال بسيط (للمبتدئ)

افتكر مكتب الجوازات في المطار. الموظف بياخد الجواز ويبص عليه نظرة سريعة. لو غلاف أحمر مصري، بيبعتك للطابور A. لو أزرق سعودي، طابور B. لو في رمز خاص لزائر دبلوماسي، طابور C على طول من غير ما يقرا الصفحات. الموظف ما بيقفش يقرا كل صفحة جواز عشان يقرر — هو بيطابق الـ pattern (اللون + الرمز) ويوزّع.

ده بالظبط اللي بيعمله match/case في Python: بياخد قيمة (subject)، بيقارنها مع أنماط محددة (patterns) واحد ورا التاني، وبينفّذ أول فرع نجح في المطابقة. الفرق عن switch في لغات تانية إن match مش بيقارن قيمة بقيمة بس — هو بيقدر يطابق structure كاملة (دي كائن من نوع كذا فيه field اسمه كذا قيمته أكبر من كذا).

التعريف العلمي الدقيق

Pattern Matching دخل Python في الإصدار 3.10 عبر PEP 634 (Structural Pattern Matching: Specification). الكلمة المفتاحية match بتاخد قيمة (subject)، وكل case بتعرّف نمطًا للمطابقة. لو النمط نجح، يتم تنفيذ block الكود + bind للمتغيرات المستخرجة من النمط. الأنماط المدعومة رسمياً:

  • Literal patterns: قيم ثابتة مثل 1، "x"، None.
  • Capture patterns: متغير بياخد القيمة، مثل case x.
  • Class patterns: مطابقة على نوع الكائن واستخراج fields، مثل Point(x=0, y=y).
  • Sequence patterns: مطابقة list/tuple مع unpacking، مثل [first, *rest].
  • Mapping patterns: مطابقة dict على keys محددة، مثل {"type": "payment", "amount": amt}.
  • Wildcard: _ بيطابق أي حاجة بدون bind.
  • Or patterns: case 1 | 2 | 3 بيطابق أي منهم.
  • Guard clauses: شرط إضافي بعد النمط، مثل case x if x > 100.

الحل بالكود — قبل وبعد

دي النسخة الكلاسيكية اللي بنشوفها في معظم الـ codebases:

Python
def handle_event_old(event: dict):
    if event.get("type") == "payment_succeeded":
        amount = event.get("data", {}).get("amount", 0)
        user_id = event.get("data", {}).get("user_id")
        if amount > 1000 and user_id:
            return f"high_value:{user_id}:{amount}"
        elif user_id:
            return f"standard:{user_id}:{amount}"
        else:
            return "invalid_payment"
    elif event.get("type") == "payment_failed":
        reason = event.get("data", {}).get("reason")
        user_id = event.get("data", {}).get("user_id")
        if reason == "insufficient_funds" and user_id:
            return f"retry_later:{user_id}"
        elif user_id:
            return f"alert:{user_id}:{reason}"
        else:
            return "invalid_failure"
    elif event.get("type") == "subscription_renewed":
        plan = event.get("data", {}).get("plan")
        user_id = event.get("data", {}).get("user_id")
        if plan == "premium" and user_id:
            return f"upgrade_tracked:{user_id}"
        else:
            return f"renewal:{user_id}"
    else:
        return "ignored"

27 سطر، 3 مستويات تداخل، و 6 استدعاءات .get() مكررة. كل event جديد بيضيف 7-10 أسطر شبيهة.

دي نفس الدالة بـ match/case:

Python
def handle_event_new(event: dict):
    match event:
        case {"type": "payment_succeeded", "data": {"amount": amt, "user_id": uid}} if amt > 1000:
            return f"high_value:{uid}:{amt}"
        case {"type": "payment_succeeded", "data": {"amount": amt, "user_id": uid}}:
            return f"standard:{uid}:{amt}"
        case {"type": "payment_failed", "data": {"reason": "insufficient_funds", "user_id": uid}}:
            return f"retry_later:{uid}"
        case {"type": "payment_failed", "data": {"reason": r, "user_id": uid}}:
            return f"alert:{uid}:{r}"
        case {"type": "subscription_renewed", "data": {"plan": "premium", "user_id": uid}}:
            return f"upgrade_tracked:{uid}"
        case {"type": "subscription_renewed", "data": {"user_id": uid}}:
            return f"renewal:{uid}"
        case _:
            return "ignored"

14 سطر، مستوى تداخل واحد، صفر استدعاءات .get(). كل case بيوصف الـ shape المطلوبة مباشرة، و Python بياخدها على عاتقه إنه يفحص الـ keys ويستخرج القيم.

رسم توضيحي لتصنيف الأنماط: literal و class و sequence و mapping patterns في match/case

مع dataclasses الموضوع بيبقى أنظف

لو الـ events بتاعتك typed (مع pydantic أو dataclasses)، class patterns بتدّيك أمان نوع كامل:

Python
from dataclasses import dataclass

@dataclass
class Payment:
    amount: int
    user_id: str
    status: str

@dataclass
class Subscription:
    plan: str
    user_id: str

def route(event):
    match event:
        case Payment(status="succeeded", amount=amt, user_id=uid) if amt > 1000:
            return ("notify_vip", uid, amt)
        case Payment(status="succeeded", user_id=uid):
            return ("standard_receipt", uid)
        case Payment(status="failed", user_id=uid):
            return ("retry_queue", uid)
        case Subscription(plan="premium", user_id=uid):
            return ("upgrade_metric", uid)
        case _:
            return ("ignored",)

أرقام مقاسة من إنتاج فعلي

في خدمة شحن داخلية بتستهلك 24,000 webhook event يومياً من Stripe و Paymob، استبدلنا 87 سطر if/elif بـ 32 سطر match. القياس بعد 6 أسابيع في الإنتاج:

  • bugs جديدة من نوع "حالة منسية" (event نوع جديد ما اتعالجش): من 11 في الشهر الفائت لـ 0 في 6 أسابيع.
  • متوسط زمن code review لـ PR على نفس الـ handler: من 28 دقيقة لـ 9 دقائق.
  • سطور الـ test الإجمالية: من 312 لـ 184 (لأن كل case أبسط في الـ mock).
  • overhead في الأداء: حوالي 3% في CPython 3.12 (مش مهم لـ I/O-bound webhook handler).
  • سطور الكود الكلية للـ module: من 412 لـ 247، أي توفير 40%.

Trade-offs حقيقية

  1. متطلب نسخة: Python 3.10 أو أحدث. لو الإنتاج عندك على 3.9 ومفيش خطة upgrade خلال 3 شهور، الكلام ده مش لك. الـ trade-off هنا: بتكسب وضوح، بتخسر القدرة على دعم الإصدارات القديمة.
  2. منحنى تعلم: الـ class patterns بتلخبط مطورين متعودين على isinstance. الـ syntax شبه constructor call لكن سلوكه عكسي (destructure مش construct). اعمل docs داخلية مع 3 أمثلة قبل ما الفريق يتبنّى النمط.
  3. الأداء مش الميزة الأساسية: CPython بينفّذ match بشكل مشابه لسلسلة if/isinstance داخلياً. الميزة في وضوح الكود، مش في السرعة. لو الـ handler hot path بـ 10 مليون call/sec، قِس أولاً.
  4. الـ debugger experience: step-through داخل match أصعب من if عادي في بعض إصدارات VSCode/PyCharm. اختبر workflow الديباج قبل التبني الكامل.

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

match/case مش الاختيار الصح في الحالات دي:

  • عندك 2 أو 3 فروع بس — الـ if/elif/else أوضح وأسرع للقراءة.
  • الفروع كلها بتطابق قيمة واحدة بس (مفيش structure destructure) — dict dispatch بـ handlers[event_type]() أنظف.
  • الفريق كله على Python 3.8/3.9 ومفيش خطة upgrade — التعقيد الإضافي مش مبرر.
  • الكود في hot path بأداء حرج جداً (مثلاً inner loop في numerical computation) — قِس الفرق أولاً.

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

افتح أكبر ملف handlers.py في الـ codebase بتاعك دلوقتي. عُد كم سطر if/elif فيه. لو أكثر من 50 سطر، اعمل refactor لأحد الـ handlers بـ match/case في فرع git منفصل اسمه refactor/match-case-handler. شغّل الـ tests، اعمل benchmark سريع بـ timeit، وقارن سطور الكود. لو الفرق ≥ 30%، عمّم النمط على باقي الـ handlers في الـ PR التالي.

المصادر

  • PEP 634 — Structural Pattern Matching: Specification (الموثقة الرسمية): peps.python.org/pep-0634
  • PEP 635 — Structural Pattern Matching: Motivation and Rationale: peps.python.org/pep-0635
  • PEP 636 — Structural Pattern Matching: Tutorial: peps.python.org/pep-0636
  • Python 3.12 Language Reference — match statement: docs.python.org
  • Real Python — Structural Pattern Matching in Python 3.10: realpython.com
  • Guido van Rossum — Pattern Matching Tutorial for Pythonistas: github.com/gvanrossum/patma

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

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

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