משתמש:Mad dr/פייתון/מודולים/re
נושא עיבוד המידע חשוב מאוד בתכנות, ולעיתים אנו נדרשים לעבד טקסט חופשי שלא מסודר בפורמט כמו json או xml. לעיתים נצטרך להבין לוג או הודעת שגיאה, ולפעמים נראה לסנן את המידע החשוב כדי להשתמש בו בהמשך. (דוגמא טובה לזה נמצאת בדרך בה אפיליקציה וובית, או אתר רגיל, "קורא" את הכתובת שלו, ונגיע לזה כנלמד על טורנדו או ג'אנגו)
re היא ספריה סטנדרטית של פייתון שנותנת לנו יכולות טובות להפעיל regular expressions (או בקיצור regex - רגקס). כאשר יש מספר שיטות לשימוש ב-re. (מי שלא בקיא ברגקסים יכול להתחיל ללמוד באתר פרל בעברית http://perl.eitan.ac.il/main.php?id=00106)
search ו-match
עריכהsearch ו-match הן הפונקציות הבסיסיות של ביטויים רגולריים, הן מקבלות שני ערכים, הראשון הוא התבנית (pattern) והשני הוא המחרוזת (subject). ערכים נוספים שאפשר לשלוח הם נקודת התחלה, נקודת סיום ומודיפקטורים (modifiers).
במידה והתבנית מתאימה למחרוזת יחזור אובייקט מסוג re.MatchObject עליו נרחיב עוד מעט, אם לא יחזור לנו None.
ההבדלים בין search ל-match מסתכמים ב:
- search עובר על כל המחרוזת עד שהוא מוצא התאמה (או לא), match מחפש את התבנית בתחילת המחרוזת (במידה ונתנו נקודת התחלה שונה, הוא יחפש ממנה כמובן)
- ההתייחסות לשורה חדשה במצב של ריבוי שורות (מודיפקטור re.M) שונה, search מתייחס לכל שורה חדשה כהתחלה (עבור התו המיוחד ^) ואילו match מתייחס רק לתחילת המחרוזת:
>>> re.match('X', 'A\nB\nX', re.MULTILINE) # No match
>>> m = re.search('^X', 'A\nB\nX', re.MULTILINE) # Match
>>> m.start()
4
>>> m.end()
5
האובייקט המוחזר הוא מסוג re.MatchObject, המתודות המעניינות שלו הן group() שמחזירה מחרוזות שמורות (אוגרים) ומקבלת את המזהה של האוגר (החל מ-1), אם מצויין אוגר 0, יוחזר ההתאמה המלאה (ללא התייחסות לאוגרים), ואם ינתנו מספר פרמטרים כל הערכים המתאימים יחזרו כ-Tuple. פנייה לאוגר שלא קיים יזרוק חריגה IndexError: no such group
המתודה groups() מחזירה את כל האוגרים כ-Tuple, ומקבלת מחרוזת כפרמטר שיהיה ערך ברירת המחדל עבור אוגרים שאין להם תוכן (לא מחרוזות ריקות)
>>> m = re.match(r"(\d+)\.?(\d+)?", "24")
>>> m.groups() # Second group defaults to None.
('24', None)
>>> m.groups('0') # Now, the second group defaults to '0'.
('24', '0')
המתודה groupdict מחזירה את האוגרים שיש להם שם ייחוס כמילון (ומתעלמת מאלו שאין להם). כדי לתת שם ייחוס משתמשים במבנה: סימן שאלה, P גדולה ושם הייחוס מוקף בסוגריים משולשים, לדוגמא:
>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+), (?P<profession>\w+)", "Albert Einstein, Physicist")
>>> m.groupdict()
{'first_name': 'Albert', 'last_name': 'Einstein', 'profession': 'Physicist'}
המתודות start ו-end מביאות את המיקום של ההתחלה והסוף של האוגר שניתן כפרמטר, אם לא צויין אוגר תחזור התוצאה המתאימה להתאמה המלאה (כמו במקרה של group(0))
>>> m.start()
0
>>> m.end()
26
>>> m.start(1)
0
>>> m.start(2)
7
>>> m.start(3)
17
>>> m.end(1)
6
>>> m.end(2)
15
>>> m.end(3)
26
קוד לדוגמא:
mad_dr@ubuntu:~$ cat url_parser.py
import re, sys, pprint
def parser(match):
if match is None:
print(" String doesn't match pattern")
return
template = ' group({}): {}'
for i in range(10):
try:
print(template.format(i, match.group(i)))
except IndexError:
if i < 3:
print(template.format(i, 'None'))
else:
break
pprint.pprint(match.groupdict())
def main():
str = sys.argv[1]
print(str)
pattern = r'(?P\w+)://(?P[^\s/]+)/(?P[^\s#?]+)#?(?P[^\s?]+)?\??(?P\S+)?'
m = re.match(pattern, str)
parser(m)
return 0
if __name__ == '__main__':
ret_code = main()
sys.exit(ret_code)
mad_dr@ubuntu:~$
mad_dr@ubuntu:~$
mad_dr@ubuntu:~$ python3 url_parser.py 'https://docs.python.org/3/library/re.html#re.sub'
https://docs.python.org/3/library/re.html#re.sub
group(0): https://docs.python.org/3/library/re.html#re.sub
group(1): https
group(2): docs.python.org
group(3): 3/library/re.html
group(4): re.sub
group(5): None
{'headline': 're.sub',
'hostname': 'docs.python.org',
'parameters': None,
'path': '3/library/re.html',
'protocol': 'https'}
compile
עריכההמתודה compile מאפשרת לנו "לשמור" ביטוי רגולרי מסויים ולהשתמש בו עבור מספר מחרוזות, לדוגמא:
>>> email_pattern = re.compile(r'\w+@\w+[.].+')
>>> p.match(r'a@a.a')
<_sre.SRE_Match object; span=(0, 5), match='a@a.a'>
>>> p.match(r'a@a')
>>> p.match(r'a@a.')
>>> p.match(r'user@host.domain')
<_sre.SRE_Match object; span=(0, 16), match='user@host.domain'>
המשתנה email_pattern מחזיק בתוכו Regular Expression Object שמאפשר אחר-כך להשתמש כמעט בכל הפונקציות האחרות של re ללא צורך לחזור על התבנית (הפרמטר הראשון יהיה כבר המחרוזת הנבדקת).
split
עריכהsplit מאפשרת לנו לחלק מחרוזת לרשימה, כאשר ההפרדה היא לפי ביטוי רגולרי, אחרי הרגקס והמחרוזת, ניתן להוסיף פרמטר שאומר כמה חלוקות לבצע, כאשר 0 יחלק את המחרוזת עד הסוף ומספר אחר יעצור את מספר החלוקות בהתאם.
בנוסף, אם הרגקס יהיה מוקף בסוגריים (בדומה להשארת אוגרים) גם ההפרדה תשמר כערך ברשימה. הדוגמא הבאה מראה את כל האפשרויות.
>>> re.split('\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split('(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split('\W+', 'Words, words, words.', 1)
['Words', 'words, words.']
>>> re.split('(\W+)', '...words, words...')
['', '...', 'words', ', ', 'words', '...', '']
שימו לב, אם ההפרדה נמצאת בהתחלה או בסוף המחרוזת תהיה מחרוזת ריקה ברשימה (בהתאם למיקום ההפרדה)
sub
עריכהsub מאפשרת לנו להחליף חלקים במחרוזת, זהו אחד המקומות בו שמירה על הערכים (מה שקראנו לו "אוגרים") יהעיל ביותר (למי שהתנסה בsed או awk הדברים ברורים)
השימוש באוגרים יכול להתבצע עם סלאש-הפוך (\) ואחריו המספר הסידורים של האוגר, או האות g ושם האוגר (או המספר שלו) בתוך סוגריים משולשים (תראו בשורה האחרונה בדוגמא).
sub מקבלת פרמטר נוסף המורה כמה החלפות יש לבצע, בדומה ל-split, המספר 0 אומר לבצע החלפה לכל מופע, ואילו מספר אחר יעצור את ההחלפות בהתאם.
>>> re.sub(r'\sAND\s', ' & ', 'Baked Beans And Rice', flags=re.IGNORECASE)
'Baked Beans & Rice'
>>> re.sub(r'(.*)\sAND\s(.*)', r'\2 & \1', 'Baked Beans And Rice', flags=re.IGNORECASE)
'Rice & Baked Beans'
>>> re.sub(r'(?P<first>.*)\sAND\s(?P<second>.*)', r'\g<2> & \g<first>', 'Baked Beans And Rice', flags=re.IGNORECASE)
'Rice & Baked Beans'
findall & finditer
עריכהfindall מחזירה את כל הערכים שמתאימים לתבנית כרשימה, ואילו finditer מחזירה אותם כאיטרטור של MatchObject (ככה שצריך לעבור עליהם בלולאה)
>>> re.findall('\D+', 'asd3fghjk')
['asd', 'fghjk']
>>> for i in re.finditer(r'(?P<name>\w+\s\w+)[,\s]*(?P<address>.*)', 'Israel Israeli, Tel Aviv\nMoishe Oofnik, Sesame Street'):
... print(i.groupdict())
...
{'address': 'Tel Aviv', 'name': 'Israel Israeli'}
{'address': 'Sesame Street', 'name': 'Moishe Oofnik'}
escape
עריכההמתודה escape היא המתודה היחידה שלא קיימת באובייקט שמוחזרת מ-compile. היא לא מקבלת רגקס לעבוד איתו, אלא פשוט מחזירה מחרוזת עם סלאש הפוך לפני כל תו מיוחד (כלומר כל תו שהוא לא אלפא-נומרי או קו תחתון)
>>> re.escape("{'address': 'Sesame Street', 'name': 'Moishe Oofnik'}")
"\\{\\'address\\'\\:\\ \\'Sesame\\ Street\\'\\,\\ \\'name\\'\\:\\ \\'Moishe\\ Oofnik\\'\\}"