Rockstar's Guide to RE
هنتكلم فى موضوع شيق "نسبيا" وهو التعابير المنتظمة Regular expressions واختصارا regex او regexp
ونستخدم ذلك التعبير كصورة مجردة للتعبير عن نص معين
سبب الأهمية
البحث عن مقطع معين بصورة معينة .. على سبيل المثال البحث عن الروابط او الصور فى صفحة ويب معينة وربما امكانية تحديث ذلك المقطع بإستبدال جزء من العنوان وحتي ربما التأكد من صحة نص معين ك أي بي او ايميل او موقع انترنت
قبل تنفيذ اي مثال تأكد من استدعاء وحدة re
import re
الفئات
الفئة بحرف صغير small تعبر عن الفئة بحرف capital تعبر عن نفي الفئة مثل
\d تعبر عن رقم
\D تعبر عن اي شئ غير الرقم
\w تعبر عن كلمة اى من الأحرف كبيرة او صغيرة بالإضافة للأرقام بالإضافة ل _
\W تعبر عن اي شئ غير الكلمة
\s تعبر عن مسافة وهى عبارة عن اي من ال \n أو \t او " “ او \r او غيرها
\S اي شئ غير المسافة
re.match (pat, string, flags=0)
>>> re.match("[0123456789]", "1")
<_sre.SRE_Match object at 0xb757a170>هنا فى ال pat وضعنا النمط او وهو [0123456789] اى ان النمط يعبر احد العناصر بين ال [ ] فإذا كان النص احدهم فينتج لنا وجود match
>>> re.match("[0-9]", "1")
<_sre.SRE_Match object at 0xb7811d40>
>>> re.match("[0-5]", "6")بالطبع استنتجت الناتج هو لاشئ لأن 6 لاتقع فى مدي التعبير للنمط وهو من 0 ل 5
هنا نفس السابق ولكن كإسلوب مختصر لكتابة النمط [0-9] اى ان النمط عبارة عن رقم من 0 ل 9
نستطيع ايضا التعبير عنها بالفئة \d اختصارا ل digit بدل من [0-9]
>>> re.match("[a-z]", "c")
<_sre.SRE_Match object at 0xb7811d40>نفس الفكرة السابقة هل يقع النص مابين مدي الحروف a-z او لأ
>>> re.match("[A-Z]", "c")
>>> re.match("[^a]", "a")
>>> re.match("[^a]", "b")
<_sre.SRE_Match object at 0xb77f3cd0>هنا استخدام ^ تعني اي شئ ماعداها
النقطة .
النقطة كنمط تعبر عن كل شئ ماعدا الأسطر الجديدة \n الا اذا حددت فى ال flags القيمة re.DOTALL
>>> re.match(".", "c")
<_sre.SRE_Match object at 0xb77f3cd0>
>>> re.match(".", "11")
<_sre.SRE_Match object at 0xb7811d40>
>>> re.match(".", " ")
<_sre.SRE_Match object at 0xb7811d40>
>>> re.match(".", "\n")
>>> re.match(".", "\n", re.DOTALL)
<_sre.SRE_Match object at 0xb77f3cd0>تعبير رمادي
>>> re.match("gr[ea]y", "grey")
<_sre.SRE_Match object at 0xb757a170>
>>> re.match("gr[ea]y", "gray")
<_sre.SRE_Match object at 0xb77f3cd0>
>>> re.match("gr[ea]y", "grly")هنا التعبير gr[ea]y يعني النمط عبارة عن حرف g و r واي من e او a وليس كلاهما متبوعا بحرف y
الآن نريد ان نعلم مالذي تم الmatch عليه
قصة حياة Match
ال match عبارة عن كائن من sre_MATCH بيعبر عن ماتطابق من النص مع النمط
>>> m=re.match("gr(e|a)y","grey")
>>> m
<_sre.SRE_Match object at 0xb77a44e0>هنا نجد كائن match عائد من مكوناته group وهي وظيفة تعبر عن النص المتطابق اذا مررت لها القيمة 0 تجد الناتج كل النص بما ان حدث تطابق واذا مررت لها ترتيب index فهو ماتطابق داخل الأقواس مثل من تطابق هل e او a فى المثال السابق
>>> m.group(0) 'grey' >>> m.group(1) 'e'
طيب انا ممكن اتلخبط مع الترتيبات .. هل ممكن اسمي مجموعة ما داخل ال match ? بالطبع!
كائن ال match بيديلك ميزة استخدام dict للحصول على المجموعة بالإسم وتمسي named group
>>> m=re.match("gr(?P<char>e|a)y","grey")
>>> m.groupdict()
{'char': 'e'}
>>> m=re.match("gr(?P<char>[ae])y","grey")
>>> m.groupdict()
{'char': 'e'}
>>> m.groupdict().get("char")
'e'وتوفر لك ايضا وظيفة span وهي تعيد لك بداية ونهاية كل مجموعة متطابقة سواء النص بأكمله او مجموعة معينة داخله
>>> m.span(1) (2, 3) >>> m.span() (0, 4)
نعم span عبارة عن ناتج وظيفتي start و end على مجموعة معينة
بداية ونهاية لتحديد بداية ونهاية التطابق استخدم ^ لتحديد البداية و $ لتحديد النهاية
>>> re.match("^real madrid$", "real madrid")
<_sre.SRE_Match object at 0xb779dbf0>
>>> re.match("^real madrid$", " real madrid")
>>> re.match("^real madrid$", "real madrid ")مالعدد المطلوب؟
احيانا نريد مثلا المطابقة على 5 ارقام او عدد مابين 2 ل 8 لاأكثر ولاأقل او ربما اي عدد من الأرقام!
الحل عبر معاملات الزيادة
{m}
بعدد مرات m
>>> m=re.match("a{3}","aaaaaa")
>>> m
<_sre.SRE_Match object at 0xb779dc28>
>>> m=re.match("a{3}","ab")
>>> m
>>>{m, n}
بعدد اقل من المرات m وعدد اكبر n
>>> m=re.match("a{3,5}","aa")
>>> m
>>> re.match("a{3,5}","aaaaa")
<_sre.SRE_Match object at 0xb779dbf0>+
بأي عدد على الأقل 1
>>> re.match("a+","b")
>>> m=re.match("a+","aaaaa")
>>> m
<_sre.SRE_Match object at 0xb779dbf0>
*اى عدد على الأقل 0
>>> re.match("a*","b")
<_sre.SRE_Match object at 0xb779dbf0>
>>> re.match("a*","aaa")
<_sre.SRE_Match object at 0xb779dc60>?
المطابقة ب 0 او 1 على الأكثر
>>> re.match("ba?","ba")
<_sre.SRE_Match object at 0xb779dbf0>
>>> re.match("ba?","bad")
<_sre.SRE_Match object at 0xb779dc60>بلاش جشع!
>>> s="SOME BOLD TEXT"
>>> m=re.match("\[.+\]", s)
>>> m
<_sre.SRE_Match object at 0xb779dc60>
>>> m.group()
'SOME BOLD TEXT'لاحظ المشكلة انها فتحت على الآخر واخدت لحد اخر قفلة قوس لكننا عايزين اول جزئية مثلا فالحل اننا نقلل الجشع لأن معاملات + و * جشعة بطبيعتها فبتاكل فى السلسلة على قد ماتقدر
>>> m=re.match("\[.+?\]", s)
>>> m.group()
''فبعد المعامل نضع علامة استفهام لنخليها راضية وغير جشعة :D
مثال
SOME BOLD TEXT
عايزين نستخلص البداية والنهاية والمحتوي
بدأنا ب
>>> m=re.match("\[(?P<bbcode>.+?)\]", s)
>>> m.groupdict('bbcode')
{'bbcode': 'b'}جيد كبداية ولكننا لم نحصل على باقي المطلوب النهاية والمحتوي
>>> m=re.match("\[(?P<bb>.+?)\](?P<text>.+)", s)
>>> m.group(2)
'SOME BOLD TEXT'الآن جيد لدينا جروب بإسم text تعبر عن الباقي (محتوي ونهاية) وهو ليس المطلوب اذا يجب ان نستثني شكل النهاية منه نجد ان النهاية تكون بادئة ب [/ ثم اسم الكود ثم غلق القوس ]
>>> m=re.match(r'\[(?P<bb>\w+)\](?P<text>[^[][\w\s]+)\[\/(?P=bb)\]', s)
>>> m.groups()
('b', 'SOME BOLD TEXT')تستطيع بدل استخدام (?P==bb ) ان تستخدم backref مثلا \1 وهي تعني اول مجموعة تم التقاطها captured وهنا هي bb
المقطع التالي مأخوذ من django/forums/fields.py
url_re = re.compile(
r'https?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain...
r'localhost|' #localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
r'(?::\d+)?' # optional port
r'(?:/?|/\S+)', re.IGNORECASE)اذا هتستخدم النمط اكثر من مرة فقم بإستخدام re.compile واستخدم وظائف match و search منه لأداء افضل
هنا مثال للتأكد على الرابط المعطي سليم او لأ
re.IGNORECASE من ال flags وتعني تجاهل حالة النص المستخدم للمطابقة
>>> url_re = re.compile(
... r'https?://' # http:// or https://
... r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain...
... r'localhost|' #localhost...
... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
... r'(?::\d+)?' # optional port
... r'(?:/?|/\S+)', re.IGNORECASE)
>>> url_re
<_sre.SRE_Pattern object at 0x9575000>
>>> url_re.match("hasdasd")
>>> url_re.match("http://programming-fr34ks.net")
<_sre.SRE_Match object at 0xb74f5598>الآن لنعمل بعض البحث قليلا من خلال re.search
لدينا نص به بعض الجمل بالإضافة لبعض عناوين المواقع التي نريد استخلاصها
>>> s2 'hello http://programming-fr34ks.net some extra http://ahmedyoussef.wordpress.com more more '
اذا استخدمنا search فانه يعيدلنا اول match
>>> m=url_re.search(s2) >>> m <_sre.SRE_Match object at 0xb74f5608> >>> m.group() 'http://programming-fr34ks.net'
اذا استخدمنا findall فستقوم بإعادة قائمة بها كل الكائات المتطابقة
>>> url_re.findall(s2) ['http://programming-fr34ks.net', 'http://ahmedyoussef.wordpress.com', '']
finditer نفس الشئ ولكن تعيد لنا iterator
الوظيفة split تقوم بتقسيم نص معين بناء على نمط وفى الغالب التقسيم على الأسطر الجديدة
الوظيفة sub, subn لاستبدال نص مطابق لتعبير منتظم فى نص ما بنص آخر
موضوعات مثل negative/positive lookahead/lookbehind خارج تغطية الموضوع
نصيحة الكاتب: استخدم http://pyparsing.wikispaces.com/ اذا تعقد الموضوع فالتعابير المنتظمة اذا تعقدت هتكره نفسك :D
للمزيد
http://docs.python.org/library/re.html
اي خطأ فى اي تعبير منتظم نرجو المعذرة :lol:
