تعلم البرمجة مع Python


الدرس: تعامل مع التعبيرات النمطية (regular expressions)


الصفحة السابقة
في هذا الفصل ، سأركز على التعبيرات النمطية  والوحدة re التي تسمح لك بمعالجتها. باختصار ، اعلم أن التعبيرات النمطية تتيح لك إجراء عمليات البحث على السلاسل بسرعة وسهولة.

هناك بالطبع العديد من الوحدات النمطية الأخرى لمعالجة النص. ومع ذلك ، سأركز اليوم ،على إعطائك الوسائل للمضي قدمًا إذا كنت ترغب في ذلك.

ما هي التعبيرات النمطية؟



التعبيرات النمطية هي طريقة فعالة للعثور على التعبيرات وعزلها عن سلسلة نصية.

للتبسيط ، تخيل أنك تصنع برنامجًا يطلب معلومات معينة من المستخدم لتخزينها في ملف. سؤاله عن اسمه واسمه الأول وبعض المعلومات الأخرى ليس بالأمر الصعب: سنستخدم الوظيفة input ونسترجع النتيجة. لا شيء جديد حتى الآن.

ولكن ماذا لو طُلب من المستخدم توفير رقم هاتف؟ ما الذي يمنعه من كتابة أي شيء؟ إذا طُلب منه عنوان بريد إلكتروني وكتب شيئًا غير صالح ، على سبيل المثال "je_te_donnerai_pas_mon_email" ، فماذا لو أردنا إرسال بريد إلكتروني تلقائيًا إلى هذا الشخص؟

إذا لم يتم التعامل مع هذه الحالة ، فقد تكون هناك مشكلة. التعبيرات النمطية هي طريقة للعثور على التعبيرات في سلسلة أو عزلها أو استبدالها. هنا ، سيسمحون لنا بالتحقق من أن رقم الهاتف الذي تم إدخاله يتكون من عشرة أرقام ، وأنه يبدأ بالرقم 0 وربما يحتوي على فواصل كل رقمين. إذا لم يكن كذلك ، يُطلب من المستخدم إدخاله مرة أخرى.

بعض عناصر بناء الجملة للتعبيرات النمطية


إذا كنت بالفعل معتادًا على التعبيرات النمطية وصياغتها ، فيمكنك التخطي إلى قسم الوحدة النمطية re . بخلاف ذلك ، يرجى العلم أنه لا يمكنني تقديم تعبيرات عادية لك إلا بإيجاز. إنه موضوع واسع للغاية يستحق كتابًا بمفرده. لا داعي للذعر ، ومع ذلك ، سأقدم لك بعض الأمثلة الملموسة ويمكنك دائمًا العثور على المزيد من التفسيرات المتعمقة في جميع أنحاء الويب.

بشكل ملموس ، كيف يتم تقديمها؟


تتيح لنا الوحدة Re ، التي سنكتشفها بعد قليل ، إجراء عمليات بحث دقيقة للغاية في سلاسل الأحرف واستبدال عناصر سلاسلنا ، كل ذلك وفقًا لمعايير معينة. هذه المعايير هي تعبيراتنا النمطية  . بالنسبة لنا ، تأتي على شكل سلاسل. يصعب قراءة التعبيرات النمطية بسرعة كبيرة ، لكن لا تقلق: سنمضي شيئًا فشيئًا.

الاحرف النمطية


عند تكوين تعبير عادي ، يمكنك استخدام أحرف خاصة وأخرى ليست كذلك. على سبيل المثال ، إذا بحثنا عن الكلمة chat في السلسلة ، فيمكننا كتابة السلسلة كتعبير عادي « chat » . حتى الآن ، لا يوجد شيء معقد للغاية.

ولكن يمكنك أن تتخيل أن التعابير النمطية لا تقتصر على هذا النوع بسيط للغاية من البحث، وإلا فإن أساليب find و replace للفئة str كان يمكن أن تكون كافيةً.

ابحث في بداية السلسلة النصية أو نهايتها


يمكنك البحث في بداية السلسلة بوضع علامة ^  في الجزء العلوي من التعبير العادي (اختصارًا للتعبير العادي  ) . إذا أردت ، على سبيل المثال ، العثور على المقطع في بداية السلسلة ، فحينئذٍ تكتب التعبير . سيتم العثور على هذا التعبير في السلسلة وليس في السلسلة .cha^cha'chaton''achat'

لتجسيد نهاية السلسلة ، ستستخدم العلامة $ . وبالتالي ،  q$ لن يتم العثور على التعبير إلا إذا كانت السلسلة تنتهي بالحرف الصغير q .

التحكم في عدد التكرارات


تسمح لنا الأحرف الخاصة التي سنكتشفها بالتحكم في عدد المرات التي يظهر فيها تعبيرنا في السلسلة.

ألق نظرة على المثال أدناه:


chat*
 
واضاف لدينا علامة النجمة ( * ) بعد الحرف t من chat . هذا يعني أن رسالتنا t يمكن إيجادها 0 ، 1 ، 2 ، ... مرة في سلسلتنا. بمعنى آخر ، chat* سيتم العثور على تعبيرنا في السلاسل التالية:  'chat' ، 'chaton'،  'chateau'،  'herbe à chat'،  'chapeau'، 'chatterton'،  'chattttttttt'...

ألق نظرة على الأمثلة أعلاه للتأكد من فهمك لها. سيتم العثور على التعبير النمطي في كل من هذه السلاسل chat* . مترجم ، يعني هذا التعبير: "نحن نبحث عن حرف c متبوعًا h بحرف a متبوعًا ، ربما ، بحرف t لا يمكن العثور عليه ، مرة واحدة أو أكثر" . لا يهم ما إذا كانت هذه الأحرف موجودة في بداية السلسلة أو في نهايتها أو في منتصفها.

مثال آخر ؟ ضع في اعتبارك التعبير النمطي أدناه وحاول فهمه:


bat*e
 
تم العثور على هذا التعبير في السلاسل التالية 'bateau'، 'batteur'و 'joan baez' .

في أمثلتنا ، * تعمل العلامة فقط على الحرف الذي يسبقها مباشرة ، وليس على الأحرف الأخرى التي تظهر قبلها أو بعدها.

هناك علامات أخرى يمكن استخدامها للتحكم في عدد تكرارات الحرف. أعطيتك ملخصًا بسيطًا في الجدول التالي ، مع أخذ أمثلة على التعبيرات مع الأحرف a ، b و c :

إشارة تفسير التعبير سلاسل تحتوي على التعبير
* 0 ، 1 أو أكثر ABC * 'ab'، 'abc'، 'abcc'،'abcccccc'
+ 1 أو أكثر abc + 'abc'و 'abcc'و'abccc'
؟ 0 أو 1 ABC؟ 'ab'و'abc'
يمكنك أيضًا التحكم بدقة في عدد التكرارات باستخدام الأقواس:
  • E{4} : يعني 4 أضعاف الحرف الكبير E ؛
  • E{2,4} : تعني 2 إلى 4 أضعاف الحرف الكبير E ؛
  • E{,5} : تعني من 0 إلى 5 أضعاف الحرف الكبير E ؛
  • E{8,} : يعني ما لا يقل عن 8 أضعاف الحرف الكبير E.

فئات الاحرف


يمكنك تحديد عدة أحرف أو فئات أحرف بين أقواس مربعة. على سبيل المثال، إذا كنت أكتب [abcd]، يعني واحدة من الرسائل من a، b، cو d .

للتعبير عن الفئات ، يمكنك استخدام الواصلة - بين حرفين. على سبيل المثال ، [A-Z] يعني التعبير "حرف كبير". يمكنك تحديد فئات أو احتمالات متعددة في تعبيرك. وبالتالي ، فإن التعبير [A-Za-z0-9]يعني "حرفًا أو حرفًا كبيرًا أو صغيرًا أو رقمًا".

يمكنك أيضًا التحكم في حدوث الفئات كما رأينا أعلاه. على سبيل المثال ، إذا كنت تريد البحث عن 5 أحرف كبيرة متتالية في سلسلة ، فسيكون تعبيرك [A-Z]{5} .

المجموعات


أعطيك الكثير من الأشياء لتتذكرها وليس لديك الفرصة لممارستها بعد. هذه هي النقطة الأخيرة التي سأركز عليها وستكون سريعة: كما قلت أعلاه ، إذا كنت تريد على سبيل المثال التحكم في عدد تكرارات حرف ما ، فأنت تضيف خلف علامة معينة ( علامة النجمة ، علامة الاستفهام ، الأقواس…). ولكن إذا كنت تريد تطبيق عنصر تحكم المثيل هذا على أحرف متعددة ، فستضع هذه الأحرف بين قوسين.


 (cha){2,5}
 
سيتم فحص هذا التعبير بحثًا عن سلاسل تحتوي على التسلسل 'cha'المكرر بين مرتين وخمس مرات. 'cha'يجب أن تتبع التسلسلات بعضها البعض بشكل طبيعي.

المجموعات مفيدة أيضًا في استبدال أجزاء من سلسلتنا ، لكننا سنصل إلى ذلك لاحقًا عندما يحين وقت التدريب ... ماذا؟ حان الوقت ؟ آه حسنًا ، دعنا نذهب ، إذن!

وحدة re



تم تصميم الوحدة re خصيصًا للعمل مع التعبيرات النمطية  . إنه يحدد العديد من الوظائف المفيدة ، التي سنكتشفها ، بالإضافة إلى كائنات محددة لنمذجة التعبيرات.

ابحث في سلسلة


سنقوم بذلك باستخدام دالة search للوحدة re . بالطبع ، لتتمكن من استخدامها ، يجب أن يتم استيرادها.


>>> import re
>>>
 
تتوقع الوظيفة Search معلمتين إلزاميتين: التعبير العادي ، في شكل سلسلة ، وسلسلة الأحرف التي نبحث فيها عن هذا التعبير. إذا تم العثور على التعبير ، تقوم الوظيفة بإرجاع كائن يرمز إلى التعبير المطلوب. خلاف ذلك ، فإنه يعيد None .

يتم تشكيل بعض الأحرف الخاصة في التعبيرات النمطية باستخدام الشرطة المائلة للخلف \ . ربما تعلم أن بايثون تمثل حروفا أخرى بهذا الرمز. إذا كتبت إلى سلسلة \n، فإن Python ستنفذ فاصل سطر!

لترمز إلى أحرف خاصة في التعبيرات النمطية ، من الضروري تجنب الشرطة المائلة للخلف بسبقها بخط مائل عكسي آخر. هذا يعني أنه لكتابة الحرف الخاص \w، عليك أن تكتب \\w .

إنه غير عملي تمامًا وأحيانًا غير مريح لسهولة القراءة. لهذا السبب أنصحك باستخدام تنسيق سلسلة لم نشاهده حتى الآن: بوضع  r قبل المحدد الذي يفتح السلسلة ، يتم إفلات جميع أحرف الشرطة المائلة للخلف الموجودة فيه.


>>> r'\n'
'\\n'
>>>
 
إذا وجدت صعوبة في رؤية النقطة ، فإنني أنصحك فقط أن تتذكر أن تضع r قبل كتابة الجمل التي تحتوي على تعبيرات ، كما سترى في الأمثلة التي سأقدمها لك.

لكن عد إلى وظيفتنا search . سوف نطبق ما رأيناه سابقًا:


>>> re.search(r"abc", "abcdef")
<_sre.SRE_Match object at 0x00AC1640>
>>> re.search(r"abc", "abacadaeaf")
>>> re.search(r"abc*", "ab")
<_sre.SRE_Match object at 0x00AC1800>
>>> re.search(r"abc*", "abccc")
<_sre.SRE_Match object at 0x00AC1640>
>>> re.search(r"chat*", "chateau")
<_sre.SRE_Match object at 0x00AC1800>
>>>
 
كما ترى ، إذا تم العثور على التعبير في السلسلة ، فسيتم _sre.SRE_Match إرجاع كائن من الفئة . إذا لم يتم العثور على التعبير ، ترجع الدالة None .

هذا يجعل من السهل للغاية معرفة ما إذا كان التعبير موجودًا في سلسلة:


if re.match(expression, chaine) is not None:
    # إذا كان التعبير في السلسلة
    # أو بشكل حدسي أكثر
if re.match(expression, chaine):
 
لا تتردد في اختبار تراكيب أكثر تعقيدًا وإفادة. خذ على سبيل المثال ، كيف تجبر المستخدم على إدخال رقم هاتف؟

من خلال الوصف الموجز الذي قدمته لك في هذا الفصل ، يمكنك تحقيق ذلك نظريًا. لكنه لا يزال تعبيرًا عاديًا معقدًا ، لذا سأعطيك إياه: خذ الوقت الكافي لإزالته إذا أردت.

يحتاج تعبيرنا العادي إلى التحقق من أن السلسلة هي رقم هاتف. يمكن للمستخدم إدخال رقم بطرق مختلفة:

  • 0X XX XX XX XX
  • 0X-XX-XX-XX-XX
  • 0X.XX.XX.XX.XX
  • 0XXXXXXXXX
بعبارات أخرى :

  • يجب أن يكون الرقم الأول 0 ؛
  • يجب أن يكون الرقم الثاني ، وكذلك كل ما يليه (9 في المجموع ، دون احتساب الصفر الأصلي) بين 0 و 9 ؛
  • كل رقمين ، يمكن أن يكون لدينا محدد اختياري (شرطة أو نقطة أو مسافة).
هذا هو التعبير المعتاد الذي أقترحه عليك:

^0[0-9]([ .-]?[0-9]{2}){4}$
 
أوو! إنه شيء غير مقروء!

أعترف أنه غير واضح إلى حد ما. دعنا نقسم الصيغة:

  • أولاً ، نجد حرف الإقحام ^مما يعني أننا نبحث عن التعبير في بداية السلسلة النصية. يمكنك أيضًا رؤية الرمز في نهاية regex $ والذي يعني أن التعبير يجب أن يكون في نهاية السلسلة. إذا كان يجب أن يكون التعبير في بداية السلسلة النصية ونهايتها ، فهذا يعني أن السلسلة التي يتم البحث عنها يجب ألا تحتوي على أي شيء آخر غير التعبير.
  • بعد ذلك ، لدينا 0 ما يعني ببساطة أن الحرف الأول من السلسلة يجب أن يكون 0 .
  • ثم لدينا فئة [0-9] . هذا يعني أنه بعد ال 0، يجب أن نجد رقمًا بين 0 و 9 (ربما 0 ، ربما 1 ، ربما 2 ...).
  • ثم يصبح الأمر معقدًا. لديك قوس يجسد بداية المجموعة. في هذه المجموعة نجد بالترتيب:
    • أولاً فئة [ .-]تعني "إما مسافة ، أو نقطة ، أو واصلة". مباشرة بعد هذه الفئة ، لديك علامة ? تعني أن هذه الفئة اختيارية.
    • بعد تحديد المُحَدِّد ، نجد فئة [0-9]تعني مرة أخرى "عددًا بين 0 و 9". بعد هذه الفئة ، في الأقواس المتعرجة ، يمكنك رؤية العدد المتوقع للأرقام (2) .
  • هذه المجموعة ، التي تحتوي على فاصل اختياري ورقمين ، يجب أن تحدث أربع مرات في تعبيرنا (بعد قوس الإغلاق ، ستجد بين الأقواس التحكم في عدد التكرارات).
إذا ألقيت نظرة فاحصة على أرقام هواتفنا ، فإنك تدرك أن التعبير العادي ينطبق على الحالات المختلفة المعروضة. تعريف رقم الهاتف الخاص بنا ليس صحيحًا لجميع الأرقام. هذا التعبير العادي هو مثال وحتى أساس لفهم المفهوم.

إذا كنت تريد أن يقوم المستخدم بإدخال رقم هاتف ، فهذا هو الكود الذي يمكنك الحصول عليه:


import re
chaine = ""
expression = r"^0[0-9]([ .-]?[0-9]{2}){4}$"
while re.search(expression, chaine) is None:
    chaine = input("Saisissez un numéro de téléphone (valide) :")
 

استبدال تعبير


الاستبدال أكثر تعقيدًا بعض الشيء. لن أعرض عليكم أي أمثلة مفيدة حقًا لأنها عادة ما تستند إلى تعبيرات يصعب فهمها.

لاستبدال جزء من سلسلة بناءً على regex ، سنستخدم وظيفة sub الوحدة النمطية re .

يتطلب ثلاث معلمات:

  • التعبير المراد البحث عنه ؛
  • ما لاستبدال هذا التعبير ؛
  • السلسلة الأصلية.
تقوم بإرجاع السلسلة المعدلة.

مجموعات مرقمة


لاستبدال جزء من التعبير ، يجب علينا أولاً استخدام المجموعات. إذا كنت تتذكر ، تظهر المجموعات بين قوسين.


 (a)b(cd)
 
في هذا المثال ، (a)هي المجموعة الأولى (cd)والثانية.

ترتيب المجموعات مهم في هذا المثال. في تعبيرنا البديل ، يمكننا الاتصال بمجموعاتنا بفضل \ . لمرة واحدة ، نحسب من 1.

ليس واضحا جدا؟ انظر إلى هذا المثال البسيط:


>>> re.sub(r"(ab)", r" \1 ", "abcdef")
' ab cdef'
>>>
 
نحن قانعون هنا ليحل محل 'ab' من قبل ' ab ' .

من المؤكد أننا كنا سنحقق نفس النتيجة باستخدام طريقة replace السلسلة الخاصة بنا. لكن التعبيرات النمطية أكثر دقة من ذلك بكثير: لقد بدأت في ملاحظة ذلك ، على ما أعتقد.

سأترك الأمر لك للبحث في السؤال ، أفضل عدم تقديم تعابير معقدة للغاية لك على الفور.

أعط أسماء لمجموعاتنا


يمكننا أيضًا إعطاء أسماء لمجموعاتنا. يمكن أن يكون أوضح من الاعتماد على الأرقام. للقيام بذلك ، يجب أن يتبع القوس الذي يفتح المجموعة علامة استفهام وحرف P كبير واسم المجموعة بين قوسين <>  .


 (?P[0-9]{2})
 
في تعبير الاستبدال ، سنستخدم التعبير \g لترميز المجموعة. لنأخذ مثالا:


>>> texte = """
... nom='Task1', id=8
... nom='Task2', id=31
... nom='Task3', id=127"""
... ...
... 
>>> print(re.sub(r"id=(?P[0-9]+)", r"id[\g]", texte))
nom='Task1', id[8]
nom='Task2', id[31]
nom='Task3', id[127]
...
>>>
 

التعبيرات المترجمة


إذا كنت تستخدم نفس التعبيرات النمطية عدة مرات في برنامجك ، فقد يكون من المفيد تجميعها .  تقدم الوحدة re الاحتفاظ بتعبيرك العادي في شكل كائن يمكنك تخزينه في برنامجك. إذا كنت بحاجة إلى البحث عن هذا التعبير في سلسلة ، فانتقل إلى طرق التعبير. يوفر لك الأداء إذا كنت تستخدم هذا التعبير كثيرًا.

على سبيل المثال ، لدي تعبير يتم استدعاؤه عندما يقوم المستخدم بإدخال كلمة المرور الخاصة به. أريد التحقق من أن كلمة المرور الخاصة به تتكون من ستة أحرف على الأقل وأنها تحتوي فقط على أحرف وأرقام كبيرة وصغيرة. هذا هو التعبير الذي أتيت إليه:


^[A-Za-z0-9]{6,}$
 
في كل مرة يقوم فيها المستخدم بإدخال كلمة مرور ، سيقوم البرنامج بالاتصال re.search للتحقق من أنها تفي بمعايير التعبير. سيكون من الأفضل الاحتفاظ بالتعبير في الذاكرة.

يتم استخدام طريقة compile الوحدة النمطية لهذا re . نقوم بتخزين القيمة المعادة (تعبير عادي مترجم) في متغير ، فهو كائن قياسي للباقي.


chn_mdp = r"^[A-Za-z0-9]{6,}$"
exp_mdp = re.compile(chn_mdp)
 
ثم يمكنك استخدام هذا التعبير المترجم مباشرة. له عدة طرق مفيدة، بما في ذلك search و sub التي رأيناها أعلاه. على عكس وظائف الوحدة التي re لها نفس الأسماء ، فإنها لا تأخذ التعبير كمعامل أول (يوجد هذا مباشرة في الكائن).

انظر بدلاً من ذلك:


chn_mdp = r"^[A-Za-z0-9]{6,}$"
exp_mdp = re.compile(chn_mdp)
mot_de_passe = ""
while exp_mdp.search(mot_de_passe) is None:
    mot_de_passe = input("Tapez votre mot de passe : ")
 

باختصار



  • تسمح لك التعبيرات النمطية بالعثور على تعبيرات معينة واستبدالها في السلاسل.
  • تسمح لك وحدة Python Re بمعالجة التعبيرات النمطية في Python .
  • تتيح لك وظيفة search الوحدة re البحثية عن تعبير في سلسلة.
  • لاستبدال تعبير معين في سلسلة ، نستخدم وظيفة sub الوحدة النمطية re .
  • يمكنك أيضًا تجميع التعبيرات النمطية باستخدام وظيفة compile الوحدة النمطية re .