ביטויים רגולריים ופייתון

09/07/2015

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

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

1. זיהוי התאמה לתבנית באמצעות ביטוי רגולרי

שתי הפונקציות בפייתון העוסקות בהתאמת ביטויים רגולריים למחרוזות הן match ו search. הראשונה תחפש התאמה בתחילת המחרוזת ואילו השניה תאפשר התאמה בכל מקום במחרוזת. 

נתחיל בכתיבת פונקציית פייתון שתקבל משפט ותוודא שכל המילים במשפט מתחילות באות גדולה. ביטויים רגולריים טובים מאוד באיתור מופע בתוך הקלט, כלומר בשאלות קיום — ולכן כשמגיעים לפתור את התרגיל באמצעות ביטוי רגולרי נרצה להפוך את השאלה, ולחפש מילה שמתחילה באות קטנה בתוך המשפט. אם לא נמצא נוכל להצהיר בבטחון כי כל המילים במשפט מתחילות באות גדולה.

הביטוי הרגולרי הבא מחפש אות קטנה בתחילת מילה:

\b[a-z]

הפונקציה search תחפש ביטוי רגולרי זה במחרוזת. במידה ותמצא נדע שהמשפט שלנו לא עומד בקריטריונים ונחזיר false. כך נראה הקוד בפייתון:

def is_title(text):
    res = re.search(r'\W[a-z]', text)
    return not res

שימו לב לתחילית r לפני המחרוזת: בדרך כלל סימן \ בתוך מחרוזת אומר לפייתון שהתו הבא יהיה תו מיוחד, למשל התו Tab מסומן באמצעות \t. במקרה של ביטוי רגולרי לסימן \ משמעות מיוחדת ואנו רוצים להעביר אותו כמו שהוא לפונקציה search. 
הוספת התחילית r למחרוזת גורמת לפייתון לא להתיחס באופן מיוחד לסימנים מיוחדים כדוגמת \ ולהשאיר אותם כמו שהם בתוך המשתנה. כך הביטוי הרגולרי שלנו מועבר כמו שהוא לפונקציה search.

2. פיצול מחרוזת לפי ביטוי רגולרי

הפונקציה split של מחרוזת טקסט מאפשרת הפרדה של המחרוזת לחלקים לפי תו או רצף תווים. הפונקציה re.split מאפשרת פיצול דומה באמצעות ביטוי רגולרי. זה יעיל כשאתם רוצים לשמור מתוך מחרוזת רק חלקים מסוימים אבל לא בדיוק יודעים מהם החלקים שצריך לשמור (אבל כן יודעים משהו על המבנה). 

כך למשל הקוד הבא מסיר את כל תגיות ה html ממחרוזת:

import re

text = re.split(r'</?\w+>', '<h1>dust in <span>the</span> wind')
# now text is:
# ['', 'dust in ', 'the', ' wind']

clean = ''.join(text)

print clean

 

3. שליפת מידע ממחרוזת באמצעות ביטוי רגולרי

לפונקציות match ו search יש תוצר לוודאי מעניין: בכל פעם שהן מוצאות התאמה הן מחזירות גם מידע לגבי אותה התאמה שנמצאה. כל התאמה מוצלחת מחזירה אובייקט מסוג SRE_Match. הפונקציות start ו end של אובייקט זה יספרו לנו היכן ההתאמה החלה והיכן הסתיימה.
אם נרצה נוכל גם לסמן בביטוי הרגולרי מקטעים לשליפה ואז אובייקט ההתאמה יכיל את הטקסט שהתאים למלים אלו. אנו מסמנים קטע לשליפה מהמחרוזת באמצעות הקפתו בסימן סוגריים עגולים. הביטוי הרגולרי הבא למשל שומר  את האות הראשונה והאות האחרונה בשורה:

^\W*(\w).*(\w)\W*$

בפייתון אנו מפעילים ביטוי זה על טקסט באמצעות פונקציית re.search שכבר הכרנו. הסוגריים העגולים בביטוי גורמים לאובייקט התוצאה להכיל מידע לגבי החלקים במחרוזת שהוקפו בסוגריים. הגישה למידע באמצעות הפונקציה groups. הפונקציה הבאה תשתמש בביטוי שהוצג כדי לזהות משפטים בהם האות הראשונה והאות האחרונה זהות (גם אם המחרוזת מסתיימת ברווחים או סימנים אחרים):

import re

def first_letter_eq_last(text):
    res = re.search(r'^\W*(\w).*(\w)\W*$', text)
    if res is not None:
        return res.group(1) == res.group(2)

text1 = 'hello world h     '
text2 = 'hello world h'

print first_letter_eq_last(text1)
print first_letter_eq_last(text2)

הפונקציה re.findall מחפשת את כל ההתאמות במחרוזת ולכן נוכל להשתמש בה כשאיננו יודעים כמה דברים אנו מחפשים. הקוד הבא למשל מוצא ומדפיס את כל האותיות הראשונות בכל אחת מהמלים:

import re

text = 'dust in the wind'
print re.findall(r'\b\w', text)

 

4. החלפת מלים בטקסט על בסיס ביטוי רגולרי

הפונקציה re.sub מאפשרת החלפת מופע של ביטוי רגולרי בטקסט רגיל (קבוע או משתנה). כך למשל נוכל למחוק רווחים כפולים במחרוזת:

import re

text = 'dust in the       wind'
print re.sub(r'\s+', ' ', text)

ıשימו לב שהפקודה לא משנה את הטקסט המקורי (ובאופן כללי בפייתון לא ניתן לשנות חלק ממחרוזת מרגע שנוצרה). הביטוי הרגולרי מורכב מתו רווח אחד או יותר וכל מופע של ביטוי זה יוחלף בתו רווח בודד. הפקודה re.sub יכולה להתמודד גם עם החלפה דינמית: כשהפרמטר השני לפקודה הוא פונקציה, הפונקציה תופעל וערך ההחזר שלה יהיה הדבר אתו נחליף את המופע. כך למשל נוכל להחליף כל אות ראשונה בכל מילה לאות גדולה:

import re

text = 'dust in the wind'

print re.sub(r'\b\w',
        lambda m: m.group(0).upper()
        , text)