2.36. קבוצות ועוגנים

תבנית יכולה לעשות יותר מאשר לומר ”מחרוזת זו מתאימה“ – היא יכולה לפרק את החלקים שהותאמו ולמסור כל אחד מהם ליישום לפי שם. סוגריים סביב חלק מתבנית הופכים אותו ל-קבוצה לוכדת; אובייקט ההתאמה חושף אז כל קבוצה כתת-מחרוזת נפרדת.

2.36.1. קבוצות לוכדות

עטפו כל חלק מתבנית ב-(...) כדי ללכוד את מה שהותאם:

>>> import re
>>> m = re.search(r'temp (\d+) at (\d+)s', 'temp 42 at 137s ok')
>>> m.group(0)
'temp 42 at 137s'
>>> m.group(1)
'42'
>>> m.group(2)
'137'
  • קבוצה 0 היא תמיד ההתאמה השלמה.

  • קבוצות 1, 2, … הן תת-המחרוזות הלכודות, ממוספרות משמאל לימין לפי סוגר הפתיחה שלהן.

  • קריאה ל-match.group() עם אינדקס מעבר לקבוצה האחרונה מעוררת IndexError.

תבנית נפוצה היא ”התאם מבנה ידוע, לכוד את החלקים המשתנים כמספרים שלמים“:

def parse_temp(line):
    m = re.search(r'temp (\d+) at (\d+)s', line)
    if not m:
        return None
    return int(m.group(1)), int(m.group(2))

2.36.2. קבוצות שאינן לוכדות

סוגריים גם מקבצים תת-ביטוי כך שכמַתֵּת (quantifier) יכול לחול על הקבוצה כולה. זו המטרה היחידה של הקיבוץ ב-r'(ab)+' – ”אחד או יותר של ab”. העובדה ש-ab מופיע כקבוצה 1 היא תופעת לוואי.

כדי לקבץ בלי ללכוד, השתמשו ב-(?:...)

>>> re.search(r'(?:ab)+', 'xababy').group(0)
'abab'

קבוצות שאינן לוכדות שומרות על מספרי הקבוצות מסודרים כאשר תבנית משתמשת בקיבוץ למבנה אך אינה מעוניינת לחלץ כל חלק.

2.36.3. עוגנים

עוגנים אינם מתאימים לתו – הם מתאימים למיקום.

  • ^ – תחילת המחרוזת.

  • $ – סוף המחרוזת.

עוגנים הם מה שגורם ל-re.match() ול-re.search() להתנהג באופן שונה. re.match(p, s) זהה ל-re.search('^' + p, s): הוא מאלץ את התבנית להתחיל במיקום 0. הוספת $ לסוף התבנית גורמת אז לתבנית להתאים למחרוזת כולה ולא לכלום מעבר לכך:

>>> re.search(r'^\d+$', '12345')
<match num=1>
>>> re.search(r'^\d+$', '12345 ok') is None
True

^ ו-$ ב-re של MicroPython תמיד מציינים את תחילת וסוף המחרוזת השלמה שהועברה ל-re.search(). אין דגל re.MULTILINE שיגרום להם להתאים בכל ירידת שורה משובצת, ו-$ אינו מתאים גם למיקום שלפני \n נגרר – הוא חייב להיות הסוף המוחלט של הקלט. כדי לקבל התנהגות לכל שורה, פצלו תחילה את הקלט לפי ירידות שורה והריצו את התבנית על כל שורה.

2.36.4. קבוצות תווים

סוגריים מרובעים מגדירים קבוצה מפורשת של תווים. ההתאמה צורכת בדיוק תו אחד מהקבוצה.

  • [abc] – אחד מתוך a, b, c.

  • [a-z] – תו אחד בטווח a-z (כולל).

  • [a-zA-Z0-9] – אותיות או ספרות. שלושה טווחים משולבים.

  • [^abc]לא אחד מתוך a, b, c. ה-^ שולל רק כאשר הוא התו הראשון בתוך הסוגריים.

דוגמאות:

>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest').group(0)
'1A2B3C'
>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest') is None
True

הקריאה הראשונה מחזירה None בפועל מכיוון שהטקסט המילולי הוא באותיות קטנות. ל-re של MicroPython אין דגל re.IGNORECASE – כדי להתאים ללא תלות ברישיות, כתבו את שני המקרים לתוך הקבוצה:

>>> re.search(r'[A-Fa-f0-9]{6}', 'colour #1a2b3c rest').group(0)
'1a2b3c'

קיצורי המחלקות (\d, \s, \w, וצורותיהם השליליות) ניתנים לשימוש גם בתוך [...]: [\w-] הוא ”תווי מילה או מקף מילולי.“

2.36.5. כמַתְּתִים חמדניים מול עצלים

הכמַתְּתִים *, +, ? ו-{m,n} הם חמדניים כברירת מחדל – הם מתאימים לכמה שיותר תווים שיתר התבנית עדיין יאפשר. לעיתים קרובות זה בדיוק מה שרוצים; לעיתים לא:

>>> re.search(r'<(.+)>', 'a <b> <c> d').group(1)
'b> <c'

ה-.+ החמדני תפס עד ה-> האחרון. הוספת ? הופכת את הכמַתֵּת לעצל – הוא מתאים לכמה שפחות:

>>> re.search(r'<(.+?)>', 'a <b> <c> d').group(1)
'b'

הצורה העצלה עוצרת ב-> הראשון. כמַתְּתִים עצלים עולים תדיר בעת חילוץ תוחמים מאוזנים ממחרוזת.

2.36.6. הפניות אחורה בהחלפה

re.sub() יכול להפנות אחורה לקבוצות שנלכדו במחרוזת ההחלפה באמצעות \1, \2, … ההחלפה משכתבת כל התאמה תוך שימוש בחלקים שנלכדו:

>>> re.sub(r'(\d+)\.(\d+)', r'\2.\1', 'swap 12.34 and 5.6')
'swap 34.12 and 6.5'

כל התאמה לוכדת שני מספרים, וההחלפה מחליפה ביניהם. \g<1> הוא תחביר חלופי לאותו דבר – שימושי כאשר התו הבא במחרוזת ההחלפה הוא ספרה (r'\g<1>0' כדי לצרף אפס מילולי לקבוצה 1 במקום לקרוא ”קבוצה 10“).

2.36.7. מה שאינו זמין

תזכורת למה ש-re של MicroPython אינו תומך בו, למקרה שתבנית מ-CPython מגיעה לכאן ומפתיעה אתכם:

  • Lookahead (?=...) ו-lookbehind (?<=...) – לא ממומשים.

  • קבוצות בעלות שם (?P<name>...) והפניות אחורה בעלות שם (?P=name) – לא ממומשים.

  • קבועי דגלים כמו re.IGNORECASE, re.MULTILINE, re.DOTALL – אינם נתמכים. בנו בעצמכם את הקבוצה ללא תלות ברישיות או פצלו מראש את הקלט.

  • המתודות match.groups(), match.span(), match.start() ו-match.end() מותנות ברמת ROM שאף לוח OpenMV שנשלח אינו מפעיל. קוד שמסתמך עליהן לא יפעל על המצלמה.

עם תבניות, קבוצות ועוגנים, ערכת כלי ה-regex על המצלמה קטנה מספיק כדי ללמוד אותה בישיבה אחת ועשירה מספיק כדי לעשות הכול פרט לניתוח תלוי-הקשר.