[مقالة] التعبيرات المنتظمة
[مقالة] التعبيرات المنتظمة
[مقالة] التعبيرات المنتظمة


هنتكلم فى موضوع شيق "نسبيا" وهو التعابير المنتظمة 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'
لاحظ المشكلة انها فتحت على الآخر واخدت لحد اخر قفلة قوس لكننا عايزين اول جزئية مثلا فالحل اننا نقلل الجشع لأن معاملات + و * جشعة بطبيعتها فبتاكل فى السلسلة على قد ماتقدر[/B]
كود:
>>> m=re.match("\[.+?\]", s)

>>> m.group()

''

فبعد المعامل نضع علامة استفهام لنخليها راضية وغير جشعة نقره لتكبير أو تصغير الصورة ونقرتين لعرض الصورة في صفحة مستقلة بحجمها الطبيعي
مثال
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 http://arabteam2000-forum.com'
اذا استخدمنا 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', 'http://arabteam2000-forum.com']

finditer نفس الشئ ولكن تعيد لنا iterator

الوظيفة split تقوم بتقسيم نص معين بناء على نمط وفى الغالب التقسيم على الأسطر الجديدة

الوظيفة sub, subn لاستبدال نص مطابق لتعبير منتظم فى نص ما بنص آخر

موضوعات مثل negative/positive lookahead/lookbehind خارج تغطية الموضوع

نصيحة الكاتب: استخدم http://pyparsing.wikispaces.com/ اذا تعقد الموضوع فالتعابير المنتظمة اذا تعقدت هتكره نفسك نقره لتكبير أو تصغير الصورة ونقرتين لعرض الصورة في صفحة مستقلة بحجمها الطبيعي

للمزيد
http://docs.python.org/library/re.html

اي خطأ فى اي تعبير منتظم نرجو المعذرة نقره لتكبير أو تصغير الصورة ونقرتين لعرض الصورة في صفحة مستقلة بحجمها الطبيعي

[مقالة] التعبيرات المنتظمة
[مقالة] التعبيرات المنتظمة
[مقالة] التعبيرات المنتظمة