Wyrażenia regularne to skuteczny język dopasowywania wzorców tekstowych. Ta strona zawiera podstawowe wprowadzenie do wyrażeń regularnych, których można używać w ćwiczeniach z języka Python, i pokazuje, jak działają w nim wyrażenia regularne. Fragment kodu Pythona umożliwia obsługę wyrażeń regularnych.
W Pythonie wyszukiwanie oparte na wyrażeniach regularnych wygląda zwykle tak:
match = re.search(pat, str)
Metoda re.search() wykorzystuje wzorzec wyrażenia regularnego oraz ciąg znaków i wyszukuje ten wzorzec w ciągu znaków. Jeśli wyszukiwanie zakończy się powodzeniem, funkcja search() zwraca obiekt dopasowania, a w przeciwnym razie zwraca wartość „Brak”. W związku z tym bezpośrednio po wyszukiwaniu następuje zwykle instrukcja „if”, aby sprawdzić, czy wyszukiwanie zakończyło się powodzeniem. W przykładzie poniżej szukamy wzorca „słowo”: a po nim słowo 3-literowe (szczegóły poniżej):
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')
Kod match = re.search(pat, str)
przechowuje wynik wyszukiwania w zmiennej o nazwie „match”. Następnie instrukcja „if” sprawdza dopasowanie – jeśli ma wartość „prawda”, wyszukiwanie zakończyło się powodzeniem, a pole match.group() to pasujący tekst (np. „word:cat”). W przeciwnym razie, jeśli dopasowanie ma wartość Fałsz (aby zwiększyć dokładność: „Brak”), wyszukiwanie nie powiodło się i nie znaleziono pasującego tekstu.
Litera „r” na początku ciągu oznaczającego „raw” dla języka Python. ciąg znaków, który przechodzi przez ukośnik lewy bez zmiany, co jest bardzo przydatne w wyrażeniach regularnych (Java bardzo tego potrzebuje). Zalecamy, aby ciągi tekstowe wzorów były zawsze zapisywane ze znakiem „r” to nawyk.
Wzory podstawowe
Wyrażenia regularne pozwalają określić wzorce, a nie tylko stałe znaki. Oto najbardziej podstawowe wzorce pasujące do pojedynczych znaków:
- a, X, 9, < – zwykłe znaki po prostu dokładnie do siebie pasują. Metaznaki, które nie pasują do siebie, ponieważ mają specjalne znaczenie, to: . ^ $ * + ? { [ ] \ | ( ) (szczegóły poniżej)
- . (kropka) – odpowiada dowolnemu pojedynczemu znakowi oprócz nowego wiersza „\n”
- \w -- (małymi w) odpowiada słowu znak: litera lub cyfra albo znak podkreślenia [a-zA-Z0-9_]. Pamiętaj, że chociaż „słowo” oznacza mnemotechnikę, pasuje tylko do pojedynczego słowa, a nie do całego słowa. \W (wielkie W) odpowiada wszystkim znakom niebędącym wyrazem.
- \b – granica między słowem a elementem niebędącym słowem
- \s -- (małymi literami s) odpowiada pojedynczemu znakowi odstępu: spacja, nowy wiersz, powrót, tabulacja, formularz [ \n\r\t\f]. \S (wielkie S) odpowiada wszystkim znakom bez odstępów.
- \t, \n, \r -- tab, nowy wiersz, powrót
- \d – cyfra dziesiętna [0–9] (niektóre starsze narzędzia do wyrażeń regularnych nie obsługują znaków \d, ale wszystkie obsługują znaki \w i \s)
- ^ = początek, $ = koniec – dopasowanie do początku lub końca ciągu
- \ -- powstrzymują „specjalność” danego postaci. Dlatego na przykład użyj \. pozwalający dopasować kropkę lub \\ do ukośnika. Jeśli nie masz pewności, czy znak ma specjalne znaczenie, na przykład „@”, możesz go poprzedzić ukośnikiem (\@). Jeśli nie jest to prawidłowa sekwencja ucieczki, np. \c, program w Pythonie zostanie zatrzymany z błędem.
Podstawowe przykłady
Dowcip: jak nazwać świnię z trzema oczami? świnia!
Podstawowe zasady wyszukiwania wyrażeń regularnych:
- Wyszukiwanie przebiega od początku do końca, zatrzymując na pierwszym znalezionym dopasowaniu
- Pasować muszą być wszystkie wzorce, ale nie cały ciąg
- Jeśli funkcja
match = re.search(pat, str)
zakończy się powodzeniem, dopasowanie nie będzie mieć wartości Brak, a konkretnie match.group() będzie pasującym tekstem
## 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"
Powtórzenia
Sytuacja staje się bardziej interesująca, gdy wskazujesz znaki + i * do określenia powtórzeń we wzorcu.
- + – co najmniej 1 wystąpienie wzorca po lewej stronie, np. „i+” = co najmniej jeden i
- * – 0 lub więcej wystąpień wzorca po lewej stronie,
- ? – dopasuj 0 lub 1 wystąpienia wzorca po lewej stronie.
Po lewej stronie i Największy
Najpierw wyszukiwanie znajduje najbardziej lewe dopasowanie do wzorca, a potem próbuje wykorzystać jak najwięcej ciągów znaków, tj. znaki + i * dochodzą jak najdalej (znak + i * jest oznaczany jako „zachęcający”).
Przykłady powtórzeń
## 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"
Przykładowe e-maile
Załóżmy, że chcesz znaleźć adres e-mail wewnątrz ciągu „xyz alice-b@google.com fioletowa małpa”. Użyjemy go jako przykładu, aby zademonstrować więcej funkcji wyrażeń regularnych. Spróbuj użyć wzorca „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 tym przypadku wyszukiwanie nie zwróci pełnego adresu e-mail, ponieważ znak „-” nie pasuje do znaku „-” lub „.” w adresie. Naprawimy to, korzystając z poniższych funkcji wyrażeń regularnych.
Nawiasy kwadratowe
Do oznaczenia zestawu znaków możesz używać nawiasów kwadratowych, tak aby wyrażenie [abc] odpowiadało literze „a” lub „b” lub „c”. Kody \w, \s itd. również działają w nawiasach kwadratowych. Jedyny wyjątek z kropką (.) oznacza po prostu kropkę dosłowną. W przypadku problemu z pocztą e-mail nawiasy kwadratowe umożliwiają łatwe dodanie „.” i „-” do zbioru znaków, które mogą występować wokół znaku @ ze wzorcem „r'[\w.-]+@[\w.-]+' aby uzyskać cały adres e-mail:
match = re.search(r'[\w.-]+@[\w.-]+', str) if match: print(match.group()) ## 'alice-b@google.com'
Wyodrębnianie grup
Grupa wyrażenia regularnego pozwala wybrać fragmenty pasującego tekstu. Załóżmy, że w przypadku problemu z pocztą e-mail chcemy wyodrębnić nazwę użytkownika i hosta oddzielnie. Aby to zrobić, dodaj nawiasy ( ) do nazwy użytkownika i nazwy hosta we wzorcu, na przykład: r'([\w.-]+)@([\w.-]+)'. W tym przypadku nawiasy nie zmieniają dopasowania wzorca, ale tworzą logiczne „grupy” wewnątrz tekstu dopasowania. Jeśli wyszukiwanie zakończy się pomyślnie, ciąg match.group(1) to tekst dopasowania odpowiadający pierwszemu lewemu nawiasowi, a match.group(2) – tekstem odpowiadającym drugiemu lewemu nawiasowi. Zwykła funkcja match.group() nadal stanowi cały tekst dopasowania.
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)
Typowym przepływem pracy w przypadku wyrażeń regularnych jest napisanie wzorca szukanego elementu i dodanie grup nawiasów w celu wyodrębnienia potrzebnych części.
Znajdź wszystko
findall() to prawdopodobnie najbardziej zaawansowana funkcja w module re. Powyżej użyliśmy funkcji re.search() w celu znalezienia pierwszego dopasowania do wzorca. findall() wyszukuje *wszystkie* dopasowania i zwraca je w postaci listy ciągów, z których każdy reprezentuje jedno dopasowanie.## 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 Z Files
W przypadku plików możesz mieć zwyczaj tworzenia pętli, by iterować wiersze w pliku, a następnie w każdym wierszu wywoływać funkcję findall(). Zamiast tego użyj funkcji findall() wykonującą iterację za Ciebie – znacznie lepiej. Wystarczy przekazać cały tekst pliku do funkcji findall() i pozwolić, aby zwracała ona listę wszystkich dopasowań w jednym kroku (pamiętaj, że funkcja f.read() zwraca cały tekst pliku w jednym ciągu):
# 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 i grupy
Mechanizm grupy nawiasów ( ) można połączyć z funkcją findall(). Jeśli wzorzec zawiera co najmniej 2 grupy nawiasów, zamiast zwracać listę ciągów, funkcja findall() zwraca listę *kropek*. Każda krotka reprezentuje jedno dopasowanie wzorca, a wewnątrz krotki znajdują się dane grupy(1), grupy(2) i ... Jeśli więc do wzorca e-maila zostaną dodane 2 grupy nawiasów, funkcja findall() zwróci listę krotek, z których każda zawiera nazwę użytkownika i hosta, np. ('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
Gdy masz już listę krotek, możesz ją zapętlić, aby wykonać obliczenia dla każdej z nich. Jeśli wzorzec nie zawiera nawiasów, funkcja findall() zwraca listę znalezionych ciągów znaków, tak jak we wcześniejszych przykładach. Jeśli wzorzec zawiera pojedynczy zestaw nawiasów, funkcja findall() zwraca listę ciągów odpowiadających danej grupie. (Niejasna funkcja opcjonalna: czasami we wzorcu występują zgrupowania w nawiasie ( ), ale nie chcesz ich wyodrębnić. W takim przypadku zapisz na początku nawiasy klamrowe ze znakiem ?:, np. (?: ), a ten nawias nie będzie uwzględniany jako wynik grupy).
Przepływ pracy i debugowanie RE
Wzorce wyrażeń regularnych mogą mieć wiele znaczenia w zaledwie kilku znakach , ale są one tak gęste, że ich debugowanie może zająć dużo czasu. Skonfiguruj środowisko wykonawcze, aby uruchomić wzorzec i z łatwością wydrukować to, co do niego pasuje, np. uruchamiając je na małym tekście testowym i wydrukując wynik funkcji findall(). Jeśli wzór nie pasuje do żadnego wzorca, spróbuj go osłabić, usuwając jego części, aby uzyskać zbyt wiele dopasowań. Gdy wynik nie jest dopasowywany, nie można zrobić żadnego postępu, ponieważ nie ma nic konkretnego do sprawdzenia. Gdy okaże się zbyt intensywny, możesz zacząć stopniowo go zmniejszać, aby osiągnąć zamierzony cel.
Opcje
Funkcje re biorą opcje umożliwiające modyfikowanie działania dopasowania do wzorca. Flaga opcji jest dodawana jako dodatkowy argument do funkcji search() lub findall() itp., np. re.search(pat, str, re.IGNORECASE).
- IGNORECASE – ignoruj wielkie i małe różnice w dopasowywaniu, np. „a” pasuje zarówno do „a”, i „A”.
- DOTALL -- allow kropka (.) do dopasowania do nowego wiersza, zwykle pasuje do wszystkiego oprócz nowego wiersza. Może to Cię pomylić, ponieważ uważasz, że fragment .* pasuje do wszystkiego, ale domyślnie nie wychodzi poza koniec wiersza. Pamiętaj, że ciąg \s (pusta przestrzeń) zawiera znaki nowego wiersza, więc jeśli chcesz dopasować ciąg znaków zawierający znaki nowego wiersza, po prostu użyj \s*
- MULTILINE – w ciągu znaków składającego się z wielu wierszy sprawdź, czy znaki ^ oraz $ pasują do początku i końca każdego wiersza. Normalnie ciąg ^/$ będzie pasować do początku i końca całego ciągu.
Chwytliwe lub niechciane (opcjonalnie)
To jest opcjonalna sekcja, która pokazuje bardziej zaawansowaną metodę wyrażenia regularnego, która nie jest potrzebna w przypadku ćwiczeń.
Załóżmy, że mamy tekst z tagami: <b>foo</b> i <i>tak dalej</i>
Załóżmy, że próbujesz dopasować każdy tag do wzorca „(<.*>)” -- co dopasowuje najpierw?
Wynik może być zaskakujący, ale dzięki zachłannemu aspektowi pliku .* pasuje on do całego elementu „<b>foo</b>” i <i>tak dalej</i>' jako jeden ważny mecz. Problem w tym, że znak .* dociera tak daleko, jak to możliwe, zamiast zatrzymywać się na pierwszym > (jest to tak zwane „chciwość”).
Wyrażenie regularne zawiera rozszerzenie, w którym dodajesz znak „?” na końcu, na przykład .*? lub .+?, zmieniając je na niechętne. Teraz przestają działać jak najszybciej. Zatem wzór „(<.*?>)” otrzyma tylko „<b>” jako pierwsze dopasowanie, a następnie „</b>” jako drugi odpowiednik i tak dalej, aby każde <..> po kolei. Zwykle używa się pliku .*? bezpośrednio po którym następuje jakiś konkretny znacznik (w tym przypadku >), do którego znaku .*? zostało wymuszone rozszerzenie uruchomienia.
Znak *? pochodzi z Perl, a wyrażenia regularne, które go obejmują, są nazywane wyrażeniami regularnymi zgodnymi z Perl – pcre. Python obsługuje pcre. Wiele narzędzi wiersza poleceń itp. ma flagi, w których akceptują wzorce pcre.
Starsza, ale powszechnie używana technika kodowania tej koncepcji „wszystkich tych znaków oprócz zatrzymania na X” korzysta ze stylu nawiasów kwadratowych. W powyższym przykładzie możesz wpisać wzorzec, ale zamiast .*, aby uzyskać wszystkie znaki, użyj wyrażenia [^>]*, który pomija wszystkie znaki inne niż >. (na początku ^ „odwraca” ustawiony nawias kwadratowy, odpowiada wszystkim znakom, których nie ma w nawiasach).
Zastępowanie (opcjonalnie)
Funkcja re.sub(pat, Replace, str) wyszukuje wszystkie wystąpienia wzorca w danym ciągu znaków i je zastępuje. Ciąg zastępczy może zawierać „\1”, „\2” które odwołują się do tekstu z grupy(1), grupa(2) itd. z oryginalnego pasującego tekstu.
Oto przykład, który powoduje wyszukanie wszystkich adresów e-mail i zmiana ich w taki sposób, aby zachować użytkownika (\1), ale hostem yo-yo-dyne.com jest 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
Ćwiczenie
Jeśli chcesz poćwiczyć wyrażenia regularne, zobacz Ćwiczenie dotyczące imion dla dzieci.