Wyrażenia regularne w Pythonie

Wyrażenia regularne to przydatny język do dopasowywania wzorców tekstowych. Ta strona zawiera podstawowe wprowadzenie do samych wyrażeń regularnych, które jest wystarczające do Twoich ćwiczeń w Pythonie, a także pokazuje, jak działają w nim wyrażenia regularne. Moduł „re” w Pythonie obsługuje wyrażenia regularne.

W Pythonie wyszukiwanie oparte na wyrażeniach regularnych jest zwykle zapisywane tak:

match = re.search(pat, str)

Metoda re.search() korzysta z wzorca wyrażenia regularnego i ciągu znaków, a następnie wyszukuje ten wzorzec w ciągu znaków. Jeśli wyszukiwanie zakończy się powodzeniem, funkcja search() zwróci obiekt dopasowania. W przeciwnym razie zwróci wartość None (Brak). Dlatego też bezpośrednio po wyszukiwaniu pojawia się instrukcja if (sprawdzanie, czy się powiodła), tak jak w poniższym przykładzie wyszukującym wzorzec „słowo:”, a następnie 3-literowe słowo (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 się powiodło, a funkcja match.group() to pasujący tekst (np. „słowo:kot”). W przeciwnym razie, jeśli dopasowanie ma wartość fałsz (aby uzyskać bardziej szczegółowe informacje), wyszukiwanie się nie powiedzie i nie ma pasującego tekstu.

Litera „r” na początku ciągu wzorcowego oznacza ciąg „surowy” Pythona, który przechodzi przez ukośnik lewy bez zmian, co jest bardzo przydatne w wyrażeniach regularnych (w języku Java ta funkcja mocno potrzebuje tej funkcji). Zalecamy, aby ciągi znaków zawierające literę „r” były zawsze zapisywane jako nawyk.

Wzorce podstawowe

Siła wyrażeń regularnych polega na tym, że mogą one określać wzorce, a nie tylko ustalone znaki. Oto najbardziej podstawowe wzorce pasujące do pojedynczych znaków:

  • a, X, 9, < – zwykłe znaki ściśle do siebie pasują. Metaznaki, które nie pasują do siebie, ponieważ mają szczególne znaczenie, to: . ^ $ * + ? { [ ] \ | ( ) (szczegóły poniżej)
  • . (a kropka) – odpowiada dowolnemu znakowi oprócz nowego wiersza „\n”
  • \w -- (małymi literami w) odpowiada znakowi „słowo”: litery lub cyfry albo podkreślenia (małych liter) [a-zA-Z0-9_]. Pamiętaj, że chociaż „słowo” jest mnemotechnikiem, dopasowywany jest tylko pojedynczy wyraz znaku, a nie całe słowo. \W (wielkie W) odpowiada dowolnemu znakowi, który nie jest słowem.
  • \b – granica między słowami i niesłowami.
  • \s -- (małymi literami s) odpowiada pojedynczemu znakowi odstępu – spacja, nowy wiersz, zwrot, tab, formularz [ \n\r\t\f]. \S (wielkie S) odpowiada dowolnemu znakowi niebędąmu znakiem odstępu.
  • \t, \n, \r -- tab, newline, return
  • \d – cyfra dziesiętna [0–9] (niektóre starsze narzędzia do obsługi wyrażeń regularnych nie obsługują parametru \d, ale wszystkie obsługują znaki \w i \s);
  • ^ = początek, $ = koniec – dopasowanie do początku lub końca ciągu
  • \ – ogranicza „specjalizację” znaku. Na przykład użyj znaku \., aby dopasować kropkę, lub znaku \\, aby dopasować ukośnik. Jeśli nie masz pewności, czy znak ma specjalne znaczenie, takie jak „@”, możesz spróbować przed nim wstawić ukośnik. Jeśli nie jest to prawidłowa sekwencja zmiany znaczenia, np. \c, program w Pythonie zostanie zatrzymany z błędem.

Podstawowe przykłady

Dowcip: jak nazwać świnię z trzema oczami? świnki!

Podstawowe zasady wyszukiwania wzorców z wyrażeniami regularnymi w ciągu znaków to:

  • Wyszukiwanie przechodzi przez ciąg od początku do końca, zatrzymując się przy pierwszym znalezionym dopasowaniu
  • Trzeba dopasować cały wzorzec, ale nie cały ciąg.
  • Jeśli funkcja match = re.search(pat, str) zakończy się powodzeniem, dopasowanie nie ma wartości Brak, a w szczególności Match.group() to pasujący tekst
  ## 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

Sprawa staje się bardziej interesująca, gdy używasz + i *, aby określić powtórzenie we wzorcu.

  • + – co najmniej 1 wystąpienie wzorca po lewej stronie, np. „i+” = co najmniej jedno wystąpienie i
  • * – co najmniej 0 wystąpień wzorca po lewej stronie.
  • ? -- dopasuj 0 lub 1 wystąpienia wzorca po jego lewej stronie

Najbardziej po lewej i największy

Najpierw wyszukiwanie znajduje najdalej lewe dopasowanie do wzorca, a drugie stara się wykorzystać jak najwięcej ciągów znaków, tj. + i *, jak najdalej (znaki + i * są „zachłanne”).

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 w ciągu „xyz alice-b@google.com fioletowa małpa”. Wykorzystamy go jako przykład, aby zademonstrować więcej funkcji wyrażeń regularnych. Oto próba użycia 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'

Wyszukiwanie nie zwróci całego adresu e-mail, ponieważ znak \w nie pasuje do znaku „-” lub „.” w adresie. Ten problem zostanie rozwiązany za pomocą poniższych funkcji związanych z wyrażeniami regularnymi.

Nawiasy kwadratowe

Nawiasy kwadratowe mogą wskazywać zestaw znaków, więc [abc] odpowiada „a”, „b” lub „c”. Kody \w, \s itp. działają też w nawiasach kwadratowych z jednym wyjątkiem. Kropka (.) oznacza tylko dosłowną kropkę. W przypadku problemów z adresami e-mail nawiasy kwadratowe są prostym sposobem na dodanie „.” i „-” do zestawu znaków, które mogą pojawić się wokół znaku @ z 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'
(Więcej funkcji nawiasów kwadratowych) Możesz też użyć myślnika, aby wskazać zakres, więc [a–z] będzie pasować do wszystkich małych liter. Aby użyć łącznika bez podawania zakresu, umieść go na końcu, np. [abc-]. Kapelusz (^) na początku zestawu nawiasów kwadratowych odwraca go, dlatego [^ab] oznacza dowolny znak oprócz „a” lub „b”.

Wyodrębnianie grup

Funkcja „grupowania” wyrażenia regularnego umożliwia wybranie części pasującego tekstu. Załóżmy, że w przypadku problemu z pocztą e-mail chcemy wyodrębnić nazwę użytkownika i hosta osobno. Aby to zrobić, dodaj nawias ( ) wokół nazwy użytkownika i hosta we wzorcu, na przykład: r'([\w.-]+)@([\w.-]+)'. W tym przypadku nawiasy nie zmieniają dopasowania do wzorca, tylko tworzą „grupy” logiczne w tekście dopasowania. W przypadku pomyślnego wyszukiwania ciąg match.group(1) to tekst pasujący do pierwszego lewego nawiasu, a dopasowanie.group(2) to tekst odpowiadający 2 lewym nawiasom. 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 z wyrażeniami regularnymi jest napisanie wzorca szukanego elementu i dodanie grup w nawiasach w celu wyodrębnienia potrzebnych części.

Znajdź wszystko

Findall() to prawdopodobnie najpotężniejsza funkcja w module re. Powyżej użyliśmy funkcji re.search(), aby znaleźć pierwsze dopasowanie dla wzorca. Funkcja findall() znajduje *wszystkie* dopasowania i zwraca je w postaci listy ciągów, przy czym każdy ciąg 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)

znajdź wszystko z plikami

W przypadku plików być może zdarza Ci się pisać pętlę do iteracji w wierszach pliku, a następnie wywoływać funkcję findall() w każdym wierszu. Zamiast tego użyj funkcji findall(), która wykona iterację za Ciebie – znacznie lepiej. Wystarczy przekazać cały tekst pliku do funkcji findall() i pozwolić, aby zwracała 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 znaków, funkcja findall() zwraca listę *tuples*. Każda krotka reprezentuje 1 dopasowanie do wzorca, a wewnątrz krotki to dane grupy(1), grupa(2) ... Jeśli więc do wzorca poczty e-mail zostaną dodane 2 grupy nawiasów, funkcja findall() zwraca listę krotek, których każda długość 2 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 jeden zestaw nawiasów, funkcja findall() zwraca listę ciągów znaków odpowiadających tej pojedynczej grupie. (Mało znana funkcja opcjonalna: czasami we wzorcu występują grupy nawiasów ( ), których nie chcesz wyodrębnić. W takim przypadku na początku nawiasów okrągłych znakiem ?: (np. (?: )) wpisz na początku nawiasy okrągłe, które nie będą uwzględniane w wynikach grupy.

Przepływ pracy i debugowanie RE

Wzorce wyrażeń regularnych wypełniają wiele znaków , ale są tak zwarte, że ich debugowanie może zająć dużo czasu. Skonfiguruj środowisko wykonawcze, aby uruchomić wzór i łatwo wydrukować jego fragment, na przykład uruchamiając go na małym tekście testowym i drukując wynik metody findall(). Jeśli wzorzec do niczego nie pasuje, spróbuj go osłabić, usuwając fragmenty, aby uzyskać zbyt wiele dopasowań. Jeśli dane nie będą pasować do niczego, nie można podejmować żadnych działań, ponieważ nie da się sprawdzić żadnych konkretów. Gdy już będzie za bardzo dopasowany, możesz stopniowo go zawężać, aby osiągnąć zamierzone cele.

Opcje

Te funkcje korzystają z opcji modyfikujących zachowanie 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 różnice między wielkimi i małymi literami przy dopasowywaniu, więc słowo „a” odpowiada zarówno nazwie „a”, jak i „A”.
  • DOTALL – zezwól na dopasowanie kropki (.) do nowego wiersza. Zwykle oznacza to dopasowanie do wszystkiego oprócz nowego wiersza. To może Cię potrącić. Myślisz, że .* pasuje do wszystkiego, ale domyślnie nie wykracza poza koniec wiersza. Pamiętaj, że znak \s (biały znak) zawiera znaki nowego wiersza, więc jeśli chcesz dopasować ciąg znaków, który może zawierać nowy wiersz, możesz użyć znaku \s*
  • MULTILINE – w ciągu znaków składającym się z wielu wierszy zezwól na dopasowanie ^ i $ do początku i końca każdego wiersza. Standardowo ^/$ będzie pasować do początku i końca całego ciągu.

szary vs. nieszarzy (opcjonalnie)

Ta sekcja jest opcjonalna i prezentuje bardziej zaawansowaną metodę wyrażeń regularnych, która nie jest potrzebna do ćwiczeń.

Załóżmy, że masz tekst z tagami: <b>foo</b> i <i>tak dalej</i>

Załóżmy, że próbujesz dopasować każdy tag do wzorca „(<.*>)” – który z nich znajduje się w pierwszej kolejności?

Wynik jest trochę zaskakujący, ale zachłanny aspekt znaku .* sprawia, że dopasowuje on całe „<b>foo</b>” i <i>tak dalej</i>” jako jedno wielkie dopasowanie. Problem polega na tym, że .* idzie tak daleko, jak to możliwe, zamiast zatrzymywać się na pierwszym znaku > (czyli jest to „zachłanność”).

Wyrażenie regularne ma rozszerzenie, w którym znak „?” , np. .*? lub .+?, przez co zmienia się na niechciwy. Teraz przestają robić to tak szybko, jak to możliwe. Wzór „(<.*?>)” będzie więc otrzymywać jako pierwsze dopasowanie tylko „<b>”, a „</b>” jako drugie i każdą parę <..> po kolei. Styl to zwykle użycie znaku .*? bezpośrednio po nim, po którym następuje konkretny znacznik (w tym przypadku >), do którego bieg .*? musi zostać wydłużony.

Rozszerzenie *? pochodzi z języka Perl. Wyrażenia regularne zawierające rozszerzenia tego języka są nazywane wyrażeniami regularnymi zgodnymi z Perl – pcre. Python obejmuje obsługę środowiska pcre. Wiele narzędzi wiersza poleceń itp. ma flagę, która pozwala akceptować wzorce pcre.

Starsza, ale powszechnie używana technika zakodowania tej koncepcji „wszystkich tych znaków z wyjątkiem zatrzymania na X” korzysta ze stylu nawiasów kwadratowych. W przypadku powyższego możesz napisać wzorzec, ale zamiast znaku .*, aby uzyskać wszystkie znaki, użyj [^>]*, który spowoduje pominięcie wszystkich znaków, które nie są > (początkowy ^ „odwraca” zestaw nawiasów kwadratowych, więc pasuje do dowolnego znaku poza nawiasem).

Zastępowanie (opcjonalnie)

Funkcja re.sub(pat, wymiana, str) wyszukuje wszystkie wystąpienia wzorca w danym ciągu i je zastępuje. Ciąg zastępczy może zawierać znaki „\1”, „\2”, które odwołują się do tekstu z grupy(1), grupy(2) itd. z oryginalnego pasującego tekstu.

Oto przykład, w którym wyszukiwanie wszystkich adresów e-mail zmienia się w taki sposób, aby zachować użytkownika (\1), ale host 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

Aby poćwiczyć wyrażenia regularne, zobacz Baby Names Ćwiczenie.