پترن‌مچینگ در پایتون + فیلم آموزشی

اشتراک‌گذاری

در نسخه ۳.۱۰ پایتون یک ویژگی جدید و جالب به زبان اضافه شده است: pattern matching(به فارسی‌: تطبیق الگو). اگر با سوئیچ‌کیس(switch-case) در زبان‌های سی و سی‌پلاس‌پلاس کار کرده‌اید، پترن‌مچینگ یک نسخه پیشرفته‌تر از switch-case مي‌باشد. در پست «از سوییچ‌کیس تا پترن‌مچینگ» از روزبه شریف‌نسب می‌توانید در مورد این ساختار‌ها در زبان‌های مختلف و تاریخچه آن‌ها بخوانید.

تا قبل از پایتون ۳.۱۰

فرض کنید کدی می‌خواهید بنویسید که دستوری از کاربر بگیرد و بر اساس آن خروجی مناسب را به کاربر نمایش دهد:

def factorial(n):
    if n <= 1:
        return 1
    else:
        return factorial(n-1) * n


print("Welcome to your cool script!")
while True:
    command = input("Cool script at your service. Enter command: ")
    # command_name arg0 arg1 arg2 ...
    args = command.split()
    command_name = args[0]
    if command_name == "Hello":
        print("Hi")
    if command_name in ("q", "quit", "Q", "Quit"):
        break
    if command_name == "Sum":
        print(sum(float(x) for x in args[1:]))
    if command_name == "Factorial":
        print(factorial(int(args[1])))

خب کدمان چند گروه از دستورات را می‌پذیرد و بر اساس دستور داده شده کار مناسب را انجام می‌دهد. برای مثال در صورتی که دستور Sum باشد، آرگامون‌ها داده شده از ورودی به عنوان عدد ممیز شناور با هم جمع می‌شوند و نتیجه به خروجی پرینت می‌شود.

در نسخه‌های پایتون قبل از ۳.۱۰ اگر می‌پرسیدید چه راه بهتری برای خلاصه کردن یک زنجیره از if ها وجود دارد بسته به کاربرد جواب یکی از این دو بود:

  • هیچ راه بهتری برای اینکار وجود ندارد.
  • باید از دیکشنری‌ها استفاده کنید.

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

کد بالا را می‌توان با استفاده از دیکشنری نوشت اما باز باید برای مواقع «ویژه» به if مراجعه کنید:

def factorial(n):
    if n <= 1:
        return 1
    else:
        return factorial(n-1) * n


COMMANDS_DICT = {
    "Hello": lambda *args: "Hi",
    "Sum": lambda *args: sum(float(x) for x in args),
    "Factorial": lambda *args: factorial(int(args[0])),
}

print("Welcome to your cool script!")
while True:
    command = input("Cool script at your service. Enter command: ")
    # command_name arg0 arg1 arg2 ...
    args = command.split()
    command_name = args[0]
    args = args[1:]
    if command_name in ("q", "quit", "Q", "Quit"):
        break
    print(COMMANDS_DICT[command_name](*args))

البته با توجه به اینکه در کد اصلی اینکه کاربر دستور خارج از انتظار را وارد کرده، چک نشده، اینجا نیز من چک نکردم. برای خارج شدن از حلقه به اجبار باید از if استفاده کرد. ولی خب با دیکشنری تمام مشکلات حل نمی‌شود و دیشکنری تنها یک مقدار را به عنوان کلید قبول می‌کند. برای اینکه ببینید در پایتون ۳.۱۰ چه ویژگی جدیدی برای روبرو شدن با این دست از مسائل به زبان اضافه شده، قسمت بعدی مطلب را بخوانید.

در پایتون ۳.۱۰ و بعد

در پایتون ۳.۱۰ یک ساختار شرطی جدید به زبان اضافه شده: match-case. اول یک برنامه ساده با استفاده از if-else می‌نویسیم و به match–case تبدیل می‌کنیم تا با این ساختار جدید آشنا شویم:

word = input("W> ")
if word == "hello":
    print("hi")
elif word == "hi":
    print("hello")
elif word == "salam":
    print("alaik")
else:
    print("Unknown word")

نسخه match-case:

word = input("W> ")
match word:
    case "hello":
        print("hi")
    case "hi":
        print("hello")
    case "salam":
        print("alaik")
    case _:
        print("Unknown word")

خب ساده هست. نه؟ پایتون تک تک case ها را نگاه می‌کند و با عملگر == چک می‌کند که word برابر با رشته مورد انتظارمان هست یا نه. نهایتا اگر هیچ‌کدام از سه case اول با مقدار متغیر word «مچ» نبودند آخرین کیس اجرا می‌شود که عملا با هرچیزی «مچ» می‌شود و مقدار متغیر word در متغیر _ ذخیره می‌شود.

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

def factorial(n):
    if n <= 1:
        return 1
    else:
        return factorial(n-1) * n

print("...")
while True:
    command = input("Enter command: ").split()
    match command:
        case ["Hello"]:
            print("Hi")
        case [("q" | "quit" | "Q" | "Quit")]:
            break
        case ["Sum", *numbers]:
            print("Summing", numbers)
            print(sum(float(x) for x in numbers))
        case ["Factorial", n]:
            print(factorial(int(n)))
        case _:
            print("Unsupported command or invalid number of arguments")

توجه کنید که چون متغیر command یک لیست می‌باشد، کیس‌ها نیز بر همین اساس نوشته شده‌اند. توضیحات کیس‌ها به ترتیب:

  • یک لیست با تک عنصر “Hello’
  • یک لیست به طول ۱ با یکی از عناصر “q” یا “Quit” یا “quit” یا “Q”
  • یک لیست با اولین عنصر “Sum” و تعداد متغیری(صفر یا بیشتر) عنصر.
  • یک لیست به طول ۲ با اولین عنصر “Factorial”
  • هرچیزی

پایتون یکی یکی case‌ها را چک کرده و زمانی که به قالب مورد انتظار رسید جملات نوشته شده را اجرا می‌کند.

(فعلا) از پترن‌مچینگ استفاده نکنید!

پایتون ۳.۱۰ زیادی جدید است و اکثر کاربران هنوز از نسخه‌های پایین‌تر استفاده می‌کنند در نتیجه ایده خوبی نیست که از این ویژگی جدید پایتون استفاده کنید تا زمانی که کامپیوتر های هدف اکثرا به پایتون ۳.۱۰ خود را ارتقاء دهند.

فیلم آموزشی

منبع

PEP 636 — Structural Pattern Matching: Tutorial

اشتراک‌گذاری