پیاده سازی عبارات switch/case به کمک دیکشنری در پایتون

python switch case

همانطور که می‌دانید در پایتون دستورات switch/case وجود ندارد. بنابراین گاهی اوقات ممکن است نیاز باشد تا عبارات طولانی شرطی به صورت if..elif..else نوشته شود. ما در اینجا قصد داریم با روشی آشنا شویم که با استفاده از دیکشنری، عبارات switch/case را شبیه سازی نماییم.

فرض کنید که قطعه کدی به صورت زیر در اختیار داریم.

>>> if cond == 'cond_a':
        handle_a()
    elif cond == 'cond_b':
        handle_b()
    else:
        handle_default()

مسلما برای بررسی ۳ حالت مختلف، این عبارت به اندازه کافی مناسب است. اما اگر تعداد حالات مورد نیاز برای بررسی بیشتر باشد باید چه کار کرد؟ اگر بخواهیم ۱۰ حالت مختف را بررسی کنیم، کد ما بسیار پیچیده و طولانی خواهد شد.

یکی از راه‌ها برای رفع این مشکل استفاده از دیکشنری‌ها است. پایتون دارای توابعی به صورت درجه یک (first-class) است؛ یعنی آن‌ها می‌توانند به عنوان آرگومان به توابع دیگر منتقل شده، به عنوان مقدار از توابع دیگر بازگردانده شوند، به متغیرها اختصاص داده شده و حتی در ساختارهای داده‌ای ذخیره شوند.

برای شفاف سازی بیشتر مثال زیر را در نظر بگیرید. در این قطعه کد یک تابع تعریف نمودیم و سپس برای دسترسی به آن در آینده، آن را در یک لیست ذخیره می‌کنیم.

>>> def myfunc(a, b):
        return a + b

>>> funcs = [myfunc]
>>> funcs[0]
<function myfunc at 0x107012230>

برای فراخوانی این تابع، باید به اندیس لیست مورد نظر دسترسی پیدا کنیم؛ و در ادامه مقادیری را به عنوان آرگومان به تابع خود انتقال دهیم.

>>> funcs[0](2, 3)
۵

حال با بکارگیری این ایده قصد داریم تا بلاک‌های if..else طولانی خود را بهینه کنیم. در ادامه یک دیکشنری تعریف کرده و سپس برای کلید‌های آن، توابعی را به عنوان مقدار در نظر می‌گیریم. به مثال زیر توجه کنید.

>>> func_dict = {
    'cond_a': handle_a,
    'cond_b': handle_b
}

در مثال بالا، handle_a و handle_b دو تابع هستند که هر کدام برای یک کلید در نظر گرفته شده‌اند. حال بجای استفاده از if/else، مقدار کلیدی را در این دیکشنری جست و جو کرده و سپس تابع مربوط به آن را فراخوانی می‌کنیم تا عملیات مورد نظر برای حالتی که قصد داریم بررسی شود، انجام گیرد.

>>> cond = 'cond_a'
>>> func_dict[cond]()

در صورتی که cond، در دیکشنری ما وجود داشته باشد، تابع مربوط به آن صدا زده خواهد شد. اما اگر این cond در دیکشنری وجود نداشته باشد، خطای KeyError به ما برگشت داده می‌شود. این خطای KeyError در واقع همان بلاک else ما خواهد بود.

برای رفع این مشکل از متد get روی دیکشنری خود استفاده می‌کنیم. با استفاده از این متد، تابعی را به عنوان مقدار پیش فرض (در صورت عدم وجود کلید در دیکشنری) نیز به عنوان آرگومان به آن پاس می‌دهیم. در صورتی که کلید موجود پیدا نشود، این تابع فراخوانی می‌شود.

>>> func_dict.get(cond, handle_default)()

قطعه کد بالا نیز همانند مثال قبل عمل می‌کند. تنها با این تفاوت که تابعی نیز به عنوان مقدار پیش فرض برای معادل سازی بلاک else در نظر گرفته شده است و همچنین از خطای KeyError نیز جلوگیری شده است.

بیایید کمی این حالت را پیچیده تر کرده و مثالی کامل تر را بررسی نماییم. پس از خواندن مثال زیر، دیگر نوشتن هرگونه عبارت if..elif..else به صورت دیکشنری برای شما ساده خواهد شد.

ابتدا تابعی را به صورت if..elif..else پیاده سازی می‌کنیم. این تابع مقداری را به عنوان آپکد دریافت می‌نماید که مشخص کننده‌ی نوع عملیات مورد نظر است (جمع، تفریق، ضرب و تقسیم)؛ و دو مقدار دریافتی دیگر با توجه به عملیات مورد نظر جمع یا ضرب یا … خواهند شد.

>>> def dispatch_if(operator, x, y):
    if operator == 'add':
        return x + y
    elif operator == 'sub':
        return x - y
    elif operator == 'mul':
        return x * y
    elif operator == 'div':
        return x / y

حال از تابع dispatch_if که در بالا نوشتیم می‌توانیم به صورت زیر استفاده کنیم.

>>> dispatch_if('mul', 2, 8)
۱۶
>>> dispatch_if('unknown', 2, 8)
None

توجه داشته باشید زمانی که از unknown به عنوان آپکد برای تابع مورد نظر استفاده می‌کنیم، مقدار None به ما نمایش داده می‌شود. چرا که پایتون به صورت ضمنی در انتهای تمام توابع عبارت return None را می‌نویسد.

حال قصد داریم تابع dispatch_if را تغییر داده تا با استفاده از دیکشنری، شرایط و حالات مختلف را بررسی نماییم. این تابع را dispatch_dict نامگذاری می‌کنیم.

>>> def dispatch_dict(operator, x, y):
    return {
        'add': lambda: x + y,
        'sub': lambda: x - y,
        'mul': lambda: x * y,
        'div': lambda: x / y,
    }.get(operator, lambda: None)()

این تابع همانند تابع قبل عمل کرده و دقیقا همان خروجی را به ما بر‌می‌گرداند.

>>> dispatch_dict('mul', 2, 8)
۱۶
>>> dispatch_dict('unknown', 2, 8)
None

راه‌های بسیاری وجود دارد تا بتوان تابع dispatch_dict را باز هم بهبود بخشید.

هر زمان که ما این تابع را فراخوانی می‌کنیم، یک دیکشنری به همراه تعدادی تابع lambda ایجاد می‌شود که از لحاظ عملکرد بسیار بد است. برای بهبود عملکرد این تابع، بهتر است تا دیکشنری مورد نظر تنها یکبار به صورت ثابت (constant) تعریف شود و زمانی که تابع فراخوانی می‌شود، به آن دیکشنری اشاره گردد. بدین ترتیب دیگر نیازی به ساخت یک دیکشنری پس از هربار فراخوانی تابع نمی‌باشد.

همچنین برای استفاده از عملگرهای ساده‌ای مانند جمع یا ضرب، بهتر است تا از توابع داخلی پایتون و کتابخانه‌ی operator (بجای تابع lambda) استفاده شود. این کتابخانه اکثر عملگرهای مورد نیاز را برای ما فراهم کرده است. مانند operator.add، operator.mul و … . اما در مثال بالا، دلیل استفاده از lambda تنها نشان دادن قابلیت پیاده سازی یک دیکشنری برای حالات مختلف و انجام یکسری عملیات بوده است.

بدین ترتیب می‌توانیم بسیاری از قطعه کدهای خود را که با عبارات طولانی if..elif..else نوشته شده‌اند، بدین صورت بازنویسی نماییم. البته توجه داشته باشید که از این تکنیک در همه‌ی شرایط استفاده نکنید. عبارات کوتاه if..else بهتر است تا به همان صورت نوشته شوند.

جوابی بنویسید:

آدرس ایمیل شما به صورت عمومی منتشر نخواهد شد.