שלוש דוגמאות ל Structural Pattern Matching ב Python 3.10
לקראת הוובינר ביום חמישי הקרוב בו נדבר לעומק על פייתון 3.10 ופיצ'ר ה Structural Pattern Matching שלו, חשבתי לאמ;לק את הסיפור עם שלוש דוגמאות קצרות שמראות איך זה עובד ולמה זה טוב.
1. טיפול במחרוזות ומספרים במכה אחת
בגיזרת התחביר החדש של הפיצ'ר יש לנו את המילה match והמילה case. אחרי match אנחנו כותבים שם של משתנה, ואז נקודותיים, ואז מתחילים לכתוב בלוקים של case. אחרי המילה case מגיע משהו שנקרא תבנית. יש הרבה סוגים של תבניות וה"התאמה" בין הקלט לתבנית היא שקובעת איזה בלוק case יבוצע. אפשר לחשוב על Pattern Matching כמו בלוק if, elif, elif, else ענק - רק בתחביר קצת יותר פשוט.
בשביל הדוגמה הראשונה נכתוב פונקציה שמקבלת "משהו" שיכול להיות מספר או מחרוזת ומדפיסה חזרה הודעה שונה לכל סוג נתונים שהתקבל. בגירסה בלי pattern matching זה היה יכול להיראות כך:
import numbers
def go(x: str|float):
if isinstance(x, str):
print(f"Welcome, {x}")
elif isinstance(x, numbers.Number):
print(f"I see you have {x} apples")
go(10)
go("ynon")
go("bye bye")
go(2.5)
ועם Pattern Match אני יכול להיפרד מהקריאות ל isinstance ולקבל את הגירסה הבאה:
import numbers
def go(x: str|float):
match x:
case str():
print(f"Welcome, {x}")
case int()|float():
print(f"I see you have {x} apples")
go(10)
go("ynon")
go("bye bye")
go(2.5)
ואגב הסוגריים העגולים אחרי הטיפוס הם מדליקים כי אפשר לרשום בתוכם מספר או מחרוזת, ואז התנאי יתאים רק אם הדבר שקיבלנו זהה בדיוק למה שבסוגריים העגולים. הקטע הבא למשל מזהה מילת קוד מיוחדת בקלט ומדפיס הודעה מיוחדת:
import numbers
def go(x: str|float):
match x:
case str("secretcode"):
print("Bravo! You Won!")
case str():
print(f"Welcome, {x}")
case int()|float():
print(f"I see you have {x} apples")
go(10)
go("ynon")
go("bye bye")
go('secretcode')
go(2.5)
2. ביצוע פקודות במבנה לא ידוע
אבל מה שבאמת נחמד ב case זה שאפשר לכתוב שם כל מיני דברים בתור תבניות, וכל תבנית עושה משהו קצת אחר. בדוגמה הקודמת התבניות היו המילים int ו str שתורגמו ל isinstance. בדוגמה הבאה אני משתמש בסוגריים מרובעים בתור תבנית כדי לבדוק שרשימה מסוימת מגיעה במבנה מסוים.
המשימה בחלק הזה היא לבנות תוכנית שמקבלת פקודות ופרמטרים לפקודות ומבצעת אותן, למשל הפקודה add תוסיף את המספר שקיבלה כפרמטר לאיזה מונה גלובאלי והפקודה print תדפיס את אותו מונה:
accumulator = 0
def process(line: str):
global accumulator
match line.split():
case ["add", n]:
accumulator += float(n)
case ["print"]:
print(accumulator)
case _:
print(f"Unknown command: {line}")
process("add 10")
process("add 20")
process("print")
process("add -30")
process("add 5")
process("print")
process("x")
שימו לב ל case האחרון עם הקו התחתי. קו תחתי מסמל תבנית Catch All שתתאים לכל מה שתתנו לה, שזה למעשה בלוק else של הקוד.
3. שימוש ב Guards
לפעמים אין לנו דרך לכתוב בדיוק את התבנית שאנחנו רוצים ובשביל זה אפשר לשלב קוד פייתון כללי שיוכל להסתכל על התאמה לתבנית ולדייק אותה. הקוד הבא הוא מימוש פשוט לפונקציית ערך מוחלט של מספר:
def myabs(n: int):
match n:
case int() if n > 0:
return n
case int() if n < 0:
return n * -1
case _:
raise Exception(f"Invalid Input {n}")
print(myabs(10))
print(myabs(-8))
print(myabs("foo"))
4. מה הלאה
כמו כל פיצ'ר חדש הכי טוב להתחיל להשתמש בו בתוכניות שלכם כדי להתרגל אליו ולהחליט מה דעתכם עליו. לאורך הזמן אתם תגלו כמה דברים שתאהבו וגם לא מעט דברים שיפתיעו אתכם.
שווה גם לקרוא את ה Tutorial שמצורף לפיצ'ר בקישור: https://www.python.org/dev/peps/pep-0636/
ואחרי שתרגישו יותר בנוח עם הפיצ'ר מומלץ לקרוא את ה PEP המלא בקישור: https://www.python.org/dev/peps/pep-0634/