Python रेगुलर एक्सप्रेशन

रेगुलर एक्सप्रेशन, टेक्स्ट पैटर्न का मिलान करने के लिए एक असरदार भाषा है. इस पेज पर बताया गया है कि रेगुलर एक्सप्रेशन कैसे काम करते हैं. यह हमारे Python अभ्यास के लिए काफ़ी है. साथ ही, यह भी बताया गया है कि Python में रेगुलर एक्सप्रेशन कैसे काम करते हैं. Python "re" मॉड्यूल, रेगुलर एक्सप्रेशन का इस्तेमाल करता है.

Python में, रेगुलर एक्सप्रेशन खोज आम तौर पर इस तरह लिखी जाती है:

match = re.search(pat, str)

re.search() तरीका, रेगुलर एक्सप्रेशन का पैटर्न और एक स्ट्रिंग लेता है. इसके बाद, स्ट्रिंग में उस पैटर्न को खोजता है. अगर खोज पूरी होती है, तो search() मैच ऑब्जेक्ट दिखाता है या कोई भी नतीजा नहीं दिखाता. इसलिए, आम तौर पर खोज के तुरंत बाद एक if-स्टेटमेंट होता है, ताकि यह पक्का किया जा सके कि उसकी खोज कामयाब रही है या नहीं, जैसा कि नीचे दिए गए उदाहरण में दिखाया गया है कि वह पैटर्न 'word:' के लिए खोज करता है. इसके बाद, तीन अक्षर वाले शब्द की खोज की जाती है (जानकारी नीचे दी गई है):

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-स्टेटमेंट, मैच की जांच करता है. अगर सही हो, तो खोज पूरी हो गई है और game.group() मेल खाने वाला टेक्स्ट है (उदाहरण के लिए, 'word:cat'). नहीं तो, अगर मैच गलत होता है (कोई भी सटीक नहीं होना चाहिए), तो खोज पूरी नहीं हो सकी और मिलता-जुलता कोई टेक्स्ट नहीं है.

पैटर्न स्ट्रिंग की शुरुआत में 'r' एक Python "रॉ" स्ट्रिंग तय करता है, जो बिना किसी बदलाव के बैकस्लैश से गुज़रती है. यह रेगुलर एक्सप्रेशन के लिए बहुत काम का है (Java को इस सुविधा की खराब ज़रूरत है!). मेरा सुझाव है कि आप हमेशा 'r' के साथ पैटर्न स्ट्रिंग को आदत की तरह लिखें.

मूल पैटर्न

रेगुलर एक्सप्रेशन की खासियत यह है कि वे फ़िक्स वर्ण ही नहीं, बल्कि पैटर्न भी तय कर सकते हैं. यहां एक वर्ण से मेल खाने वाले सबसे बुनियादी पैटर्न दिए गए हैं:

  • a, X, 9, < -- सामान्य वर्ण खुद से पूरी तरह मेल खाते हैं. वे मेटा-कैरेक्टर जो अपने-आप मेल नहीं खाते, क्योंकि उनका खास मतलब होता है: . ^ $ * + ? { [ ] \ | ( ) (जानकारी नीचे दी गई है)
  • . (a पीरियड) -- नई लाइन '\n' को छोड़कर किसी भी एक वर्ण से मेल खाता है
  • \w -- (लोअरकेस w) किसी "शब्द" वर्ण से मेल खाता है: अक्षर, अंक या अंडरबार [a-zA-Z0-9_]. ध्यान दें कि इसके लिए "शब्द" एक स्मेनिक है, लेकिन यह सिर्फ़ एक शब्द चार से मेल खाता है, पूरे शब्द से नहीं. \W (अपर केस W) बिना शब्द वाले वर्ण से मैच करता है.
  • \b -- शब्द और गैर-शब्द के बीच की सीमा
  • \s -- (लोअरकेस s), सिंगल व्हाइटस्पेस वर्ण से मेल खाता है -- स्पेस, नई लाइन, रिटर्न, टैब, फ़ॉर्म [ \n\r\t\f]. \S (अपर केस S), किसी भी खाली जगह वाले वर्ण से मैच नहीं करता है.
  • \t, \n, \r -- टैब, नई लाइन, रिटर्न
  • \d -- दशमलव अंक [0-9] (कुछ पुराने रेगुलर एक्सप्रेशन सुविधाएं \d पर काम नहीं करतीं, लेकिन वे सभी \w और \s के साथ काम करती हैं)
  • ^ = शुरू, $ = अंत -- स्ट्रिंग की शुरुआत या आखिर से मेल करें
  • \ -- किसी वर्ण की "विशेषज्ञता" को रोकना. उदाहरण के लिए, पीरियड का मिलान करने के लिए \. या स्लैश का मिलान करने के लिए \\ का इस्तेमाल करें. अगर आपको पक्के तौर पर नहीं पता कि किसी वर्ण का कोई खास मतलब है, जैसे कि '@', तो इसके आगे स्लैश लगाने की कोशिश करें, \@. अगर यह एस्केप सीक्वेंस, जैसे कि \c नहीं है, तो आपका Python प्रोग्राम एक गड़बड़ी के साथ रुक जाएगा.

बुनियादी उदाहरण

मज़ाक़: तीन आंखों वाले सूअर को क्या कहा जाता है? पिग!

स्ट्रिंग में किसी पैटर्न को खोजने के लिए, रेगुलर एक्सप्रेशन खोजने के बुनियादी नियम ये हैं:

  • खोज स्ट्रिंग में शुरू से आखिर तक आगे बढ़ती है, लेकिन मिलते-जुलते पहले नतीजे पर रुक जाती है
  • सभी पैटर्न का मेल खाना ज़रूरी है, लेकिन सभी स्ट्रिंग से नहीं
  • अगर match = re.search(pat, str) काम करता है, तो मैच कोई नहीं नहीं है. खास तौर पर, 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
  • * -- इसके बाईं ओर पैटर्न की 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'
(स्क्वेयर-ब्रैकेट की अन्य सुविधाएं) किसी रेंज को दिखाने के लिए, डैश का इस्तेमाल भी किया जा सकता है. इससे [a-z], अंग्रेज़ी के सभी छोटे अक्षरों से मैच करता है. रेंज को बताए बिना डैश का इस्तेमाल करने के लिए, आखिरी बार डैश लगाएं, जैसे कि [abc-]. स्क्वेयर-ब्रैकेट सेट की शुरुआत में अप-हैट (^) होने से उसे उलटा होता है. इसलिए, [^ab] का मतलब 'a' या 'b' को छोड़कर कोई भी वर्ण है.

समूह एक्सट्रैक्शन

रेगुलर एक्सप्रेशन की "ग्रुप" सुविधा, आपको मेल खाने वाले टेक्स्ट के हिस्सों को चुनने की सुविधा देती है. मान लें कि ईमेल से जुड़ी उस समस्या के लिए हम उपयोगकर्ता नाम और होस्ट को अलग-अलग निकालना चाहते हैं. ऐसा करने के लिए, उपयोगकर्ता नाम के आस-पास ब्रैकेट ( ) जोड़ें और पैटर्न में होस्ट करें, जैसे: r'([\w.-]+)@([\w.-]+)'. इस मामले में, ब्रैकेट से पैटर्न में बदलाव नहीं होता है, बल्कि वे मैच टेक्स्ट में लॉजिकल "ग्रुप" सेट करते हैं. सफल खोज पर, पहले बाएं ब्रैकेट से जुड़ा मैच टेक्स्ट Match.group(1) है और दूसरे बाएं ब्रैकेट से जुड़ा टेक्स्ट Match.group(2) है. सादा game.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)

रेगुलर एक्सप्रेशन के साथ एक सामान्य वर्कफ़्लो यह है कि आप अपनी पसंद की चीज़ के लिए एक पैटर्न लिखते हैं, अपनी पसंद के हिस्सों को एक्सट्रैक्ट करने के लिए ब्रैकेट (कोष्ठक) ग्रुप जोड़ते हैं.

Findall

Findall(), री मॉड्यूल में शायद सबसे असरदार फ़ंक्शन है. ऊपर हमने किसी पैटर्न का पहला मैच ढूंढने के लिए 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)

Files के साथ Findall

फ़ाइलों के लिए, आपकी आदत हो सकती है कि आप फ़ाइल की लाइन को फिर से दोहराने के लिए एक लूप लिखें. इसके बाद, हर लाइन पर 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

ब्रैकेट ( ) ग्रुप बनाने के तरीके को Findall() के साथ जोड़ा जा सकता है. अगर पैटर्न में दो या उससे ज़्यादा ब्रैकेट वाले ग्रुप शामिल हैं, तो स्ट्रिंग की सूची दिखाने के बजाय, Findall() *tuples* की सूची दिखाता है. हर टपल, पैटर्न का एक मैच दिखाता है. टपल के अंदर ग्रुप(1), ग्रुप(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

ट्यूपल की सूची मिल जाने पर, हर टपल का हिसाब लगाने के लिए उस पर लूप किया जा सकता है. अगर पैटर्न में कोई ब्रैकेट मौजूद नहीं है, तो findall(), पाई गई स्ट्रिंग की सूची दिखाता है. जैसा कि पहले के उदाहरणों में बताया गया है. अगर पैटर्न में ब्रैकेट का एक सेट शामिल है, तो findall() उसी ग्रुप से जुड़ी स्ट्रिंग की सूची दिखाता है. (धुंधला वैकल्पिक सुविधा: कभी-कभी आपके पास पैटर्न में पार्सन ( ) वाले ग्रुप होते हैं, लेकिन जिन्हें आपको निकालना नहीं होता. इस मामले में, शुरुआत में ?: के साथ कोष्ठक लिखें, जैसे कि (?: ) और उस बाएं कोष्ठक को ग्रुप के नतीजे के रूप में नहीं गिना जाएगा.)

आरई वर्कफ़्लो और डीबग

रेगुलर एक्सप्रेशन पैटर्न में कुछ ही वर्णों में बहुत सारा मतलब निकाला जाता है, लेकिन वे इतने घनत्व वाले होते हैं कि आपको अपने पैटर्न को डीबग करने में काफ़ी समय बिताना पड़ सकता है. अपना रनटाइम सेट अप करें, ताकि आप पैटर्न को चला सकें और उससे मेल खाने वाली चीज़ों को आसानी से प्रिंट कर सकें. उदाहरण के लिए, इसे छोटे टेस्ट टेक्स्ट पर चलाकर और Findall() के नतीजे को प्रिंट करके. अगर पैटर्न किसी भी मैच से मैच नहीं करता, तो पैटर्न को कमज़ोर करने की कोशिश करें और इसके कुछ हिस्सों को हटाएं. इससे आपको बहुत ज़्यादा मैच मिलेंगे. जब यह किसी भी चीज़ से मेल नहीं खाता है, तो आप कोई प्रगति नहीं कर सकते, क्योंकि देखने के लिए कुछ भी ठोस नहीं है. एक बार जब यह बहुत ज़्यादा मैच करने लगे, तो इसे धीरे-धीरे बढ़ाने और अपने हिसाब से कोशिश करें.

विकल्प

री फ़ंक्शन, पैटर्न मैच के व्यवहार में बदलाव करने के लिए विकल्प लेते हैं. विकल्प फ़्लैग को search() या findall() वगैरह में एक अतिरिक्त तर्क के रूप में जोड़ा जाता है. उदाहरण के लिए, re.search(pat, str, re.IGNORECASE).

  • IGNORECASE -- मैच करने के लिए अपर/लोअरकेस के अंतर को अनदेखा करता है, इसलिए 'a', 'a' और 'A', दोनों से मैच करता है.
  • डॉटऑल -- डॉट (.) को नई लाइन से मेल खाने की अनुमति दें -- आम तौर पर यह नई लाइन से मेल खाने के अलावा किसी भी चीज़ से मेल खाता है. इससे आपको परेशानी हो सकती है -- आपको लगता है कि .* हर चीज़ से मेल खाता है, लेकिन डिफ़ॉल्ट रूप से यह लाइन के आखिर से आगे नहीं जाता. ध्यान दें कि \s (व्हाइटस्पेस) में नई लाइनें शामिल होती हैं, इसलिए अगर आपको खाली सफ़ेद जगह को मैच करना है, जिसमें नई लाइन शामिल हो सकती है, तो सिर्फ़ \s* का इस्तेमाल करें
  • मल्टीलाइन - कई लाइनों से बनी स्ट्रिंग में, हर लाइन के शुरू और आखिर का मिलान करने के लिए ^ और $ का इस्तेमाल किया जाता है. आम तौर पर, ^/$ पूरी स्ट्रिंग की शुरुआत और आखिर से मेल खाएगा.

लालची बनाम गैर-लालची (ज़रूरी नहीं)

यह एक वैकल्पिक सेक्शन है जो ऐसी ऐडवांस रेगुलर एक्सप्रेशन तकनीक दिखाता है जिसकी कसरतों के लिए ज़रूरत नहीं है.

मान लीजिए कि आपके पास टैग वाले टेक्स्ट हैं: <b>foo</b> और <i>ऐसा आगे भी</i> है

मान लीजिए कि आप हर टैग को पैटर्न '(<.*>)' से मैच करने की कोशिश कर रहे हैं -- पहले यह किस चीज़ से मेल खाता है?

नतीजा थोड़ा हैरान करने वाला है, लेकिन .* के लालच वाले पहलू की वजह से यह एक बड़े मिलान के रूप में पूरे '<b>foo</b> और <i>इसके बाद भी</i>' से मेल खाता है. समस्या यह है कि पहले > (यानी "लालची") पर रुकने के बजाय, .* जितना हो सकता है उतना लंबा होता है.

रेगुलर एक्सप्रेशन में एक एक्सटेंशन है जहां कोई ? आखिर में, .*? या .+?, जैसे कि टेक्स्ट को लालच के बिना बदलें. अब वे जल्द से जल्द रुक जाते हैं. इसलिए, पैटर्न '(<.*?>)' को पहले मैच के तौर पर सिर्फ़ '<b>' और दूसरे मैच के लिए '</b>' मिलेगा. इसी तरह, हर <..> की जोड़ी मिलती रहेगी. आम तौर पर, इसकी स्टाइल ऐसी होती है जिसमें .*? के तुरंत बाद कुछ कंक्रीट मार्कर (> इस मामले में) का इस्तेमाल किया जाता है, जिसमें .*? रन को ज़बरदस्ती बढ़ाया जाता है.

*? एक्सटेंशन Perl से शुरू हुआ, और रेगुलर एक्सप्रेशन वाले रेगुलर एक्सप्रेशन को Perl के साथ काम करने वाले रेगुलर एक्सप्रेशन -- pcre के रूप में जाना जाता है. Python में pcre का भी इस्तेमाल किया जा सकता है. कई कमांड लाइन का इस्तेमाल होता है वगैरह में एक फ़्लैग होता है, जहां वे कंप्यूटर पैटर्न को स्वीकार करते हैं.

"X पर रुकने को छोड़कर इन सभी वर्ण" के इस आइडिया को कोड करने के लिए, एक पुरानी, लेकिन बड़े पैमाने पर इस्तेमाल की जाने वाली तकनीक, स्क्वेयर-ब्रैकेट स्टाइल का इस्तेमाल करती है. ऊपर दिए गए पैटर्न के लिए, पैटर्न लिखा जा सकता है, लेकिन .* के बजाय सभी वर्ण पाने के लिए, [^>]* का इस्तेमाल करें. यह उन सभी वर्णों को छोड़ देता है जो > नहीं हैं. आगे वाला ^, स्क्वेयर ब्रैकेट के सेट को "इनवर्ट" करता है, ताकि वह ऐसे किसी भी वर्ण से मेल खाए जो ब्रैकेट में नहीं है.

सब्सिटिट्यूशन (ज़रूरी नहीं)

re.sub(pat,Replace, str) फ़ंक्शन दी गई स्ट्रिंग में पैटर्न के सभी इंस्टेंस खोजता है और उन्हें बदल देता है. रिप्लेसमेंट स्ट्रिंग में '\1', '\2' शामिल हो सकते हैं. ये ग्रुप(1), ग्रुप(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

ड्रिल

रेगुलर एक्सप्रेशन का अभ्यास करने के लिए, बच्चों के नाम की कसरत देखें.