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 על המצלמה קטנה מספיק כדי ללמוד אותה בישיבה אחת ועשירה מספיק כדי לעשות הכול פרט לניתוח תלוי-הקשר.