عبارات منظم پایتون

عبارات منظم زبان قدرتمندی برای تطبیق الگوهای متن هستند. این صفحه یک مقدمه اولیه برای عبارات منظم برای تمرینات پایتون ما کافی است و نشان می دهد که چگونه عبارات منظم در پایتون کار می کنند. ماژول "re" پایتون پشتیبانی از عبارت منظم را ارائه می دهد.

در پایتون جستجوی عبارات منظم معمولاً به صورت زیر نوشته می شود:

match = re.search(pat, str)

متد re.search() یک الگوی عبارت منظم و یک رشته را می گیرد و آن الگو را در رشته جستجو می کند. اگر جستجو موفقیت آمیز باشد، search() یک شی مطابق یا None را در غیر این صورت برمی گرداند. بنابراین، جستجو معمولاً بلافاصله با یک عبارت if دنبال می‌شود تا بررسی شود که آیا جستجو موفق بوده است یا خیر، همانطور که در مثال زیر نشان داده شده است که الگوی "word:" را به دنبال یک کلمه 3 حرفی جستجو می‌کند (جزئیات زیر):

import re

str = 'an example word:cat!!'
match = re.search(r'word:\w\w\w', str)
# If-statement after search() tests if it succeeded
if match:
  print('found', match.group()) ## 'found word:cat'
else:
  print('did not find')

کد match = re.search(pat, str) نتیجه جستجو را در متغیری به نام "match" ذخیره می کند. سپس if-statement مطابقت را آزمایش می‌کند -- اگر درست باشد، جستجو با موفقیت انجام شد و match.group() متن منطبق است (به عنوان مثال 'word:cat'). در غیر این صورت، اگر مطابقت نادرست باشد (به طور دقیق تر، هیچ کدام)، در این صورت جستجو موفقیت آمیز نبوده و متن مطابقی وجود ندارد.

"r" در ابتدای رشته الگو، یک رشته "خام" پایتون را مشخص می کند که بدون تغییر از میان اسلش های عقب عبور می کند که برای عبارات منظم بسیار مفید است (جاوا به شدت به این ویژگی نیاز دارد!). توصیه می‌کنم همیشه به عنوان یک عادت، رشته‌های الگو را با r بنویسید.

الگوهای اساسی

قدرت عبارات منظم این است که می توانند الگوها را مشخص کنند، نه فقط کاراکترهای ثابت را. در اینجا ابتدایی ترین الگوهایی هستند که با کاراکترهای تک مطابقت دارند:

  • a, X, 9, < -- کاراکترهای معمولی دقیقاً با خودشان مطابقت دارند. متا کاراکترهایی که به دلیل داشتن معانی خاص با خودشان مطابقت ندارند عبارتند از: . ^ $ * + ? { [ ] \ | ( ) (جزئیات زیر)
  • . (یک نقطه) -- با هر کاراکتری به جز خط جدید '\n' مطابقت دارد
  • \w -- (کوچک w) با یک کاراکتر "کلمه" مطابقت دارد: یک حرف یا رقم یا زیر نوار [a-zA-Z0-9_]. توجه داشته باشید که اگرچه "واژه" یادگاری برای این است، اما فقط با یک کلمه منفرد مطابقت دارد، نه کل کلمه. \W (حرف بزرگ W) با هر کاراکتر غیر کلمه ای مطابقت دارد.
  • \b -- مرز بین کلمه و غیر کلمه
  • \s -- (s) با یک کاراکتر فضای خالی - فاصله، خط جدید، بازگشت، برگه، فرم [\n\r\t\f] مطابقت دارد. \S (حرف بزرگ S) با هر کاراکتر غیر فضای سفید مطابقت دارد.
  • \t، \n، \r -- برگه، خط جدید، بازگشت
  • \d -- رقم اعشاری [0-9] (برخی ابزارهای regex قدیمی از \d پشتیبانی نمی‌کنند، اما همه آنها \w و \s را پشتیبانی می‌کنند)
  • ^ = شروع، $ = پایان -- با شروع یا پایان رشته مطابقت دارد
  • \ -- از "ویژگی" یک شخصیت جلوگیری می کند. بنابراین، برای مثال، از \ استفاده کنید. برای مطابقت با نقطه یا \\ برای مطابقت با یک اسلش. اگر مطمئن نیستید که یک کاراکتر دارای معنای خاصی است، مانند '@'، می توانید یک اسلش جلوی آن، \@ قرار دهید. اگر یک دنباله فرار معتبر نباشد، مانند \c، برنامه پایتون شما با یک خطا متوقف می شود.

مثال های اساسی

جوک: به خوک سه چشمی چه می گویید؟ خوب!

قوانین اساسی جستجوی عبارت منظم برای یک الگو در یک رشته عبارتند از:

  • جستجو از ابتدا تا انتها از طریق رشته ادامه می یابد و در اولین بازی یافت شده متوقف می شود
  • همه الگو باید مطابقت داشته باشد، اما نه همه رشته
  • اگر match = re.search(pat, str) موفقیت آمیز باشد، match None نیست و به ویژه match.group() متن منطبق است.
  ## Search for pattern 'iii' in string 'piiig'.
  ## All of the pattern must match, but it may appear anywhere.
  ## On success, match.group() is matched text.
  match = re.search(r'iii', 'piiig') # found, match.group() == "iii"
  match = re.search(r'igs', 'piiig') # not found, match == None

  ## . = any char but \n
  match = re.search(r'..g', 'piiig') # found, match.group() == "iig"

  ## \d = digit char, \w = word char
  match = re.search(r'\d\d\d', 'p123g') # found, match.group() == "123"
  match = re.search(r'\w\w\w', '@@abcd!!') # found, match.group() == "abc"

تکرار

وقتی از + و * برای مشخص کردن تکرار در الگو استفاده می کنید، چیزها جالب تر می شوند

  • + -- 1 یا چند مورد از الگو در سمت چپ آن، به عنوان مثال 'i+' = یک یا چند i's
  • * -- 0 یا بیشتر از الگوی سمت چپ آن رخ می دهد
  • ? - 0 یا 1 وقوع الگو را در سمت چپ آن مطابقت دهید

سمت چپ ترین و بزرگ ترین

ابتدا جستجو سمت چپ ترین مورد را برای الگو پیدا می کند، و دوم سعی می کند تا جایی که ممکن است از رشته استفاده کند - به عنوان مثال + و * تا آنجا که ممکن است (+ و * "طمع" هستند).

مثال های تکرار

  ## i+ = one or more i's, as many as possible.
  match = re.search(r'pi+', 'piiig') # found, match.group() == "piii"

  ## Finds the first/leftmost solution, and within it drives the +
  ## as far as possible (aka 'leftmost and largest').
  ## In this example, note that it does not get to the second set of i's.
  match = re.search(r'i+', 'piigiiii') # found, match.group() == "ii"

  ## \s* = zero or more whitespace chars
  ## Here look for 3 digits, possibly separated by whitespace.
  match = re.search(r'\d\s*\d\s*\d', 'xx1 2   3xx') # found, match.group() == "1 2   3"
  match = re.search(r'\d\s*\d\s*\d', 'xx12  3xx') # found, match.group() == "12  3"
  match = re.search(r'\d\s*\d\s*\d', 'xx123xx') # found, match.group() == "123"

  ## ^ = matches the start of string, so this fails:
  match = re.search(r'^b\w+', 'foobar') # not found, match == None
  ## but without the ^ it succeeds:
  match = re.search(r'b\w+', 'foobar') # found, match.group() == "bar"

مثال ایمیل

فرض کنید می‌خواهید آدرس ایمیل را در رشته «xyz alice-b@google.com بنفش میمون» پیدا کنید. ما از این به عنوان یک مثال در حال اجرا برای نشان دادن ویژگی های بیان منظم تر استفاده می کنیم. در اینجا تلاشی با استفاده از الگوی r'\w+@\w+' ارائه شده است:

  str = 'purple alice-b@google.com monkey dishwasher'
  match = re.search(r'\w+@\w+', str)
  if match:
    print(match.group())  ## 'b@google'

جستجو در این مورد کل آدرس ایمیل را دریافت نمی کند زیرا \w با '-' یا ' مطابقت ندارد. در آدرس ما این مشکل را با استفاده از ویژگی های عبارت معمولی در زیر حل خواهیم کرد.

براکت های مربعی

از براکت های مربع می توان برای نشان دادن مجموعه ای از نویسه ها استفاده کرد، بنابراین [abc] با 'a' یا 'b' یا 'c' مطابقت دارد. کدهای \w، \s و غیره نیز در داخل کروشه‌های مربع کار می‌کنند، با یک استثنا که نقطه (.) فقط به معنای یک نقطه تحت اللفظی است. برای مشکل ایمیل‌ها، براکت‌های مربع راه آسانی برای اضافه کردن «» هستند. و '-' به مجموعه نویسه هایی که می توانند در اطراف @ با الگوی r'[\w.-]+@[\w.-]+ ظاهر شوند تا کل آدرس ایمیل را دریافت کنید:

  match = re.search(r'[\w.-]+@[\w.-]+', str)
  if match:
    print(match.group())  ## 'alice-b@google.com'
(بیشتر ویژگی‌های براکت مربع) همچنین می‌توانید از خط تیره برای نشان دادن محدوده استفاده کنید، بنابراین [az] با تمام حروف کوچک مطابقت دارد. برای استفاده از خط تیره بدون نشان دادن محدوده، خط تیره را در آخر قرار دهید، به عنوان مثال [abc-]. یک کلاه بالا (^) در ابتدای یک مجموعه براکت مربع آن را معکوس می کند، بنابراین [^ab] به معنای هر علامتی به جز 'a' یا 'b' است.

استخراج گروهی

ویژگی "گروهی" یک عبارت منظم به شما امکان می دهد بخش هایی از متن مطابق را انتخاب کنید. فرض کنید برای مشکل ایمیل ها می خواهیم نام کاربری و هاست را جداگانه استخراج کنیم. برای انجام این کار، پرانتز ( ) را در اطراف نام کاربری و میزبان در الگو اضافه کنید، مانند: r'([\w.-]+)@([\w.-]+)'. در این مورد، پرانتزها الگوی مطابقت را تغییر نمی‌دهند، در عوض آنها «گروه‌های» منطقی را در متن مطابقت ایجاد می‌کنند. در یک جستجوی موفق، match.group(1) متن مطابق با اولین پرانتز سمت چپ و match.group(2) متن مربوط به دومین پرانتز سمت چپ است. مطابق معمول () match.group همچنان کل متن مطابقت است.

  str = 'purple alice-b@google.com monkey dishwasher'
  match = re.search(r'([\w.-]+)@([\w.-]+)', str)
  if match:
    print(match.group())   ## 'alice-b@google.com' (the whole match)
    print(match.group(1))  ## 'alice-b' (the username, group 1)
    print(match.group(2))  ## 'google.com' (the host, group 2)

یک گردش کار رایج با عبارات منظم این است که شما یک الگو برای چیزی که به دنبال آن هستید می نویسید و گروه های پرانتز را برای استخراج قسمت های مورد نظر خود اضافه می کنید.

پیدا کردن

()finall احتمالاً قدرتمندترین تابع در ماژول re است. در بالا از re.search() برای یافتن اولین تطابق برای یک الگو استفاده کردیم. findall() *همه* تطابق ها را پیدا می کند و آنها را به عنوان لیستی از رشته ها برمی گرداند که هر رشته نشان دهنده یک تطابق است.
  ## Suppose we have a text with many email addresses
  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'

  ## Here re.findall() returns a list of all the found email strings
  emails = re.findall(r'[\w\.-]+@[\w\.-]+', str) ## ['alice@google.com', 'bob@abc.com']
  for email in emails:
    # do something with each found email string
    print(email)

Findall With Files

برای فایل‌ها، ممکن است عادت داشته باشید که یک حلقه بنویسید تا روی خطوط فایل تکرار شود، و سپس می‌توانید findall() را در هر خط فراخوانی کنید. در عوض، اجازه دهید findall() تکرار را برای شما انجام دهد - خیلی بهتر! فقط کل متن فایل را به findall() وارد کنید و اجازه دهید لیستی از تمام موارد منطبق را در یک مرحله برگرداند (به یاد بیاورید که f.read() کل متن یک فایل را در یک رشته برمی گرداند):

  # Open file
  f = open('test.txt', encoding='utf-8')
  # Feed the file text into findall(); it returns a list of all the found strings
  strings = re.findall(r'some pattern', f.read())

findall و Groups

مکانیسم گروه پرانتز () را می توان با ()finall ترکیب کرد. اگر الگو شامل 2 یا چند گروه پرانتز باشد، به جای برگرداندن لیستی از رشته ها، findall() لیستی از *tuples* را برمی گرداند. هر تاپل یک تطابق از الگو را نشان می‌دهد و داخل تاپل داده‌های گروه(1)، گروه(2) .. است. بنابراین اگر 2 گروه پرانتز به الگوی ایمیل اضافه شوند، آنگاه findall() لیستی از تاپل ها را برمی گرداند که هر طول 2 حاوی نام کاربری و میزبان است، به عنوان مثال ('alice', 'google.com').

  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
  tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str)
  print(tuples)  ## [('alice', 'google.com'), ('bob', 'abc.com')]
  for tuple in tuples:
    print(tuple[0])  ## username
    print(tuple[1])  ## host

هنگامی که لیست تاپل ها را دارید، می توانید روی آن حلقه بزنید تا برای هر تاپل مقداری محاسبات انجام دهید. اگر الگو فاقد پرانتز باشد، آنگاه ()finall لیستی از رشته های یافت شده را مانند مثال های قبلی برمی گرداند. اگر الگو شامل یک مجموعه پرانتز باشد، آنگاه ()finall لیستی از رشته‌های مربوط به آن گروه را برمی‌گرداند. (ویژگی اختیاری مبهم: گاهی اوقات شما گروه بندی های پرانتزی ( ) در الگو دارید، اما نمی خواهید آنها را استخراج کنید. در این صورت، پرانتزها را با یک ?: در ابتدا بنویسید، به عنوان مثال (?: ) و آن پرانتز سمت چپ می خواهد به عنوان یک نتیجه گروهی محاسبه نمی شود.)

RE Workflow و Debug

الگوهای بیان منظم معنی زیادی را فقط در چند کاراکتر بسته بندی می کنند، اما آنقدر متراکم هستند که می توانید زمان زیادی را صرف اشکال زدایی الگوهای خود کنید. زمان اجرا خود را طوری تنظیم کنید که بتوانید یک الگو را اجرا کنید و آنچه را که مطابقت دارد به راحتی چاپ کنید، برای مثال با اجرای آن بر روی یک متن آزمایشی کوچک و چاپ نتیجه ()finall(. اگر الگو با چیزی مطابقت ندارد، سعی کنید الگو را ضعیف کنید، قسمت هایی از آن را بردارید تا تعداد زیادی مطابقت داشته باشید. وقتی با چیزی مطابقت ندارد، نمی‌توانید پیشرفتی داشته باشید، زیرا هیچ چیز مشخصی برای بررسی وجود ندارد. هنگامی که بیش از حد منطبق شد، می توانید آن را به صورت تدریجی سفت کنید تا به چیزی که می خواهید برسد.

گزینه ها

توابع re گزینه هایی را برای تغییر رفتار مطابقت الگو انتخاب می کنند. پرچم گزینه به عنوان یک آرگومان اضافی به search() یا findall() و غیره اضافه می شود، به عنوان مثال re.search(pat, str, re.IGNORECASE).

  • IGNORECASE - تفاوت‌های بزرگ/کوچک را برای تطبیق نادیده بگیرید، بنابراین «a» با «a» و «A» مطابقت دارد.
  • DOTALL -- اجازه دهید نقطه (.) با خط جدید مطابقت داشته باشد -- معمولاً با هر چیزی به جز خط جدید مطابقت دارد. این می تواند شما را به هم بریزد -- فکر می کنید .* با همه چیز مطابقت دارد، اما به طور پیش فرض از انتهای یک خط عبور نمی کند. توجه داشته باشید که \s (فضای سفید) شامل خطوط جدید است، بنابراین اگر می‌خواهید با یک فضای خالی که ممکن است شامل یک خط جدید باشد مطابقت دهید، فقط می‌توانید از \s* استفاده کنید.
  • MULTILINE - در یک رشته ساخته شده از چندین خط، اجازه دهید ^ و $ با شروع و پایان هر خط مطابقت داشته باشند. به طور معمول ^/$ فقط با شروع و پایان کل رشته مطابقت دارد.

حریص در مقابل غیر حریص (اختیاری)

این بخش اختیاری است که تکنیک بیان منظم پیشرفته تری را نشان می دهد که برای تمرینات مورد نیاز نیست.

فرض کنید متنی با برچسب‌ها در آن دارید: <b>foo</b> و <i>و به همین ترتیب</i>

فرض کنید می‌خواهید هر تگ را با الگوی "(<.*>)" مطابقت دهید -- ابتدا با چه چیزی مطابقت دارد؟

نتیجه کمی تعجب آور است، اما جنبه حریصانه .* باعث می شود که کل «<b>foo</b> و <i>و به همین ترتیب</i>» را به عنوان یک مسابقه بزرگ مطابقت دهد. مشکل این است که .* تا جایی که می تواند پیش می رود، به جای توقف در اولین > (معروف به "طمع" است).

یک پسوند برای عبارت منظم وجود دارد که در آن یک ? در پایان، مانند .*؟ یا .+؟، تغییر آنها به غیر حریص بودن. حالا هر چه زودتر متوقف می شوند. بنابراین الگوی "(<.*?>)" فقط "<b>" را به عنوان تطبیق اول، و "</b>" را به عنوان تطبیق دوم دریافت می کند، و به همین ترتیب هر جفت <..> را به نوبه خود دریافت می کند. سبک معمولاً به این صورت است که شما از .* استفاده می کنید؟ بلافاصله پس از آن مقداری نشانگر بتن (> در این مورد) که .*؟ اجرا مجبور به تمدید می شود.

*؟ پسوند از Perl منشا گرفته است، و عبارات منظم که شامل پسوندهای Perl هستند به عنوان Perl Compatible Regular Expressions -- pcre شناخته می شوند. پایتون شامل پشتیبانی pcre است. بسیاری از ابزارهای خط فرمان و غیره دارای یک پرچم هستند که در آن الگوهای pcre را می پذیرند.

یک تکنیک قدیمی‌تر اما پرکاربرد برای کدگذاری این ایده «همه این نویسه‌ها به جز توقف در X» از سبک براکت مربع استفاده می‌کند. برای موارد بالا می توانید الگو را بنویسید، اما به جای .* برای به دست آوردن همه نویسه ها، از [^>]* استفاده کنید که از روی همه کاراکترهایی که > نیستند رد می شود (^ پیشرو مجموعه براکت را معکوس می کند، بنابراین مطابقت دارد. هر کاراکتری که در پرانتز نباشد).

تعویض (اختیاری)

تابع re.sub(pat,placement,str) تمام نمونه های الگو را در رشته داده شده جستجو می کند و آنها را جایگزین می کند. رشته جایگزین می تواند شامل '\1'، '\2' باشد که به متنی از group(1)، group(2) و غیره از متن تطبیق اصلی اشاره دارد.

در اینجا یک مثال است که تمام آدرس‌های ایمیل را جستجو می‌کند و آنها را تغییر می‌دهد تا کاربر (\1) باقی بماند، اما yo-yo-dyne.com به عنوان میزبان باشد.

  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
  ## re.sub(pat, replacement, str) -- returns new string with all replacements,
  ## \1 is group(1), \2 group(2) in the replacement
  print(re.sub(r'([\w\.-]+)@([\w\.-]+)', r'\1@yo-yo-dyne.com', str))
  ## purple alice@yo-yo-dyne.com, blah monkey bob@yo-yo-dyne.com blah dishwasher

ورزش کنید

برای تمرین عبارات منظم، به تمرین نام‌های نوزاد مراجعه کنید.