
Wyrażenia regularne w Pythonie (regex) – moduł re w praktyce
Spis treści
- Wyrażenia regularne w Pythonie (regex) – moduł re w praktyce
- Czym są wyrażenia regularne?
- Kiedy warto używać wyrażeń regularnych?
- Jak zacząć pracę z regex w Pythonie?
- Składnia wyrażeń regularnych – najważniejsze elementy
- Moduł
re– przegląd funkcji- Funkcja
re.search()– wyszukiwanie pierwszego dopasowania - Funkcje
re.match()ire.fullmatch()– dopasowanie od początku i do końca tekstu - Grupy nazwane w wyrażeniach regularnych –
(?P<nazwa>...) - Lista wszystkich dopasowań
re.findall() - Dopasowania z pozycją
re.finditer() - Podział tekstu na fragmenty
re.split() - Znajdź i zamień
re.sub()
- Funkcja
- Zachłanne i leniwe dopasowanie
- Flagi: wielkość liter, wiele linii, komentarze
- Kompilacja wyrażeń
re.compile() - Gotowe wzorce do typowych zadań
- Najczęstsze błędy
- Ściągawka (cheatsheet)
- FAQ – najczęściej zadawane pytania
- Podsumowanie
Z artykułu dowiesz się, czym są wyrażenia regularne (ang. regular expressions, w skrócie regex lub regexp) i jak korzystać z nich w Pythonie za pomocą wbudowanego modułu re.
Na końcu znajdziesz gotowe wzorce, listę typowych błędów i ściągawkę.
Czym są wyrażenia regularne?
Wyrażenie regularne to wzorzec, który opisuje wygląd poszukiwanego fragmentu tekstu. Zamiast wskazywać konkretne słowo, określa się jego cechy np. "ciąg pięciu cyfr".
Elementy, z których budujemy wzorzec:
- zwykłe znaki, takie jak litery i cyfry
- znaki specjalne o ustalonym znaczeniu np. symbol
\doznacza dowolną cyfrę, a symbol+wskazuje, że poprzedzający element może wystąpić wielokrotnie.
Program analizuje tekst i porównuje go ze wzorcem. W ten sposób ustala, czy w treści znajduje się pasujący do wzorca fragment i w którym miejscu on występuje.
Przykładowe znaki specjalne:
\d– dowolna cyfra (0–9).– dowolny znak[a-z]– dowolna mała litera od a do z+– "poprzedni element jeden lub więcej razy".
Przykładowo wzorzec \d\d-\d\d\d opisuje każdy polski kod pocztowy – nie musisz znać konkretnego kodu, wystarczy, że znasz jego kształt: dwie cyfry, myślnik, trzy cyfry.
Wyrażenia regularne nie są specyfiką języka Python. Te same (lub bardzo podobne) wzorce działają w JavaScript, Javie, C#, PHP, w edytorach kodu (VS Code, PyCharm, Notepad++), w bazach danych (PostgreSQL, Oracle) oraz w narzędziach systemowych, takich jak
grepczysed. Raz poznana składnia wyrażeń regularnych przyda ci się w wielu miejscach.
Kiedy warto używać wyrażeń regularnych?
Wyrażenia regularne sprawdzają się tam, gdzie szukasz tekstu o określonej budowie, a nie konkretnego napisu.
Przykładowe obszary, w których możemy ich używać:
- walidacja danych – czy użytkownik wpisał poprawny kod pocztowy, numer telefonu, adres e-mail, datę
- wyciąganie informacji – wszystkie adresy e-mail z dokumentu, kwoty z faktury, daty z logów serwera
- czyszczenie danych – usuwanie podwójnych spacji, znaków specjalnych, ujednolicanie formatów
- masowa zamiana tekstu – zmiana formatu dat z
2026-09-21na21.09.2026w tysiącach plików - dzielenie tekstu – rozbijanie na słowa lub pola, gdy separatorów jest kilka.
Wyrażenia regularne nie są dobrym wyborem, gdy:
-
szukasz konkretnego, niezmiennego ciągu znaków – tutaj wystarczą wbudowane narzędzia do pracy z tekstem: operator
inoraz metodystr.find(),str.replace(),str.split(), czystr.startswith(). Działają szybciej, a kod jest prostszy w czytaniu. -
przetwarzasz dane ustrukturyzowane – do obsługi formatów takich jak HTML, XML, czy JSON lepiej użyć przeznaczonych do tego bibliotek, np. BeautifulSoup, xml.etree lub json. Poradzą sobie z zagnieżdżeniami i nietypowymi przypadkami, przy których wyrażenia regularne łatwo zawodzą.
Jak zacząć pracę z regex w Pythonie?
Do pracy z wyrażeniami regularnymi w Pythonie służy moduł re. Jest on częścią biblioteki standardowej, więc nie trzeba go instalować – wystarczy import.
Na początek wyszukamy w tekście polski kod pocztowy za pomocą funkcji re.search().
import re
tekst = 'Zamówienie wysłano na adres: ul. Raszyńska 34, 05-816 Michałowice'wynik = re.search(r'\d\d-\d\d\d', tekst)if wynik: print('Znaleziono kod pocztowy:', wynik.group()) # Znaleziono kod pocztowy: 05-816Wyjaśnienie kodu:
-
import re– importujemy modułre, który zawiera funkcje do pracy z wyrażeniami regularnymi -
tekst = '...'– tekst, w którym szukamy kodu pocztowego -
r'\d\d-\d\d\d'– wzorzec, czyli opis tego, czego szukamy. Symbol\doznacza dowolną cyfrę, więc wzorzec pasuje do układu: dwie cyfry, myślnik, trzy cyfry. Literarprzed cudzysłowem oznacza surowy napis (raw string), dzięki któremu Python nie traktuje ukośnika\jako znaku specjalnego -
wynik = re.search(wzorzec, tekst)– funkcja przeszukuje tekst od lewej do prawej i zatrzymuje się na pierwszym fragmencie pasującym do wzorca. Liczba34z numeru domu nie pasuje, gdyż po niej nie ma myślnika. Jeśli dopasowanie zostanie znalezione, zmiennawynikzawiera obiekt dopasowania (typure.Match) z informacjami o znalezionym fragmencie. Jeśli nie – ma wartośćNone -
if wynik:– sprawdzamy, czy cokolwiek znaleziono. Bez tego warunku próba odczytania wyniku przy braku dopasowania zakończyłaby się błędem. Więcej o konstrukcjiifprzeczytasz w artykule Instrukcje warunkowe w Pythonie – if, elif, else -
wynik.group()– metoda obiektu dopasowania, która zwraca znaleziony fragment tekstu, w tym przypadku05-816 -
print()– wyświetla na ekranie komunikat wraz ze znalezionym kodem pocztowym. To jedna z funkcji wbudowanych Pythona, dostępnych bez importowania żadnego modułu. Więcej o nich przeczytasz w artykule Funkcje wbudowane w Pythonie.
Składnia wyrażeń regularnych – najważniejsze elementy
Zwykłe znaki i znaki specjalne
Większość znaków we wzorcu jest dopasowywana dosłownie. Wzorzec "kot" znajdzie w tekście ciąg liter "kot" – zarówno w słowie "kot", jak i np. w "kotlet", czy "szkoła kotów".
Wyjątkiem są znaki specjalne (metaznaki). Nie oznaczają one siebie, tylko pełnią określoną funkcję we wzorcu np. kropka oznacza dowolny znak, a gwiazdka – powtórzenie:
. ^ $ * + ? { } [ ] \ | ( )Jeśli chcesz wyszukać metaznak dosłownie, poprzedź go ukośnikiem wstecznym (backslashem) \. Taki zabieg nazywamy escapowaniem znaku. Przykładowo:
\.oznacza kropkę\?oznacza znak zapytania\(oznacza nawias otwierający.
Różnicę dobrze widać na przykładzie funkcji re.findall(), która zwraca listę wszystkich dopasowań w tekście:
import re
# ['a', '.', 'b'] – kropka = dowolny znakprint(re.findall(r'.', 'a.b'))# ['.'] – \. = tylko kropkaprint(re.findall(r'\.', 'a.b'))W pierwszym przypadku kropka działa jak metaznak i dopasowuje każdy znak po kolei.
W drugim, dzięki ukośnikowi wstecznemu, traktowana jest dosłownie, więc znaleziona zostaje tylko kropka.
Klasy znaków w wyrażeniach regularnych: \d, \w, \s i [...]
Klasa znaków opisuje jeden znak pasujący do określonego zbioru.
Zamiast wypisywać wszystkie cyfry, czy litery, możesz użyć gotowego skrótu np. \d, albo zdefiniować własny zbiór w nawiasach kwadratowych np. [aeiou].
| Wzorzec | Znaczenie | Przykład dopasowania |
|---|---|---|
\d | dowolna cyfra | 7 |
\D | dowolny znak, który nie jest cyfrą | a, -, spacja |
\w | litera (także polska), cyfra lub _ | ż, A, 5, _ |
\s | biały znak: spacja, tabulator, nowa linia | spacja, \t |
\S | dowolny znak, który nie jest białym znakiem | x,%, 7 |
. | dowolny znak oprócz nowej linii | x, %, 7 |
[aeiou] | jedna z wymienionych liter | e |
[a-z] | jedna mała litera z zakresu a–z (bez polskich znaków) | k |
[0-9A-F] | cyfra szesnastkowa | B |
[^0-9] | dowolny znak, który nie jest cyfrą (^ na początku nawiasu oznacza negację) | x |
Wielkie litery odwracają znaczenie:
\D– znak, który nie jest cyfrą\W– znak, który nie jest literą, cyfrą ani podkreślnikiem\S– znak, który nie jest białym znakiem.
Warto też wiedzieć, że wewnątrz nawiasów kwadratowych większość metaznaków traci specjalne znaczenie,
więc [.] oznacza zwykłą kropkę, a nie dowolny znak.
Wewnątrz nawiasów kwadratowych specjalne znaczenie zachowują tylko cztery znaki:
\– nadal służy do escapowania i tworzenia skrótów, więc[\d.]oznacza cyfrę lub kropkę, a[\]]– zamykający nawias kwadratowy]– zamyka klasę znaków. Dosłownie trzeba go zapisać jako\]albo umieścić na samym początku np.[]a]^– oznacza negację, ale tylko na początku nawiasu. W innym miejscu to zwykły znak, np.[a^]pasuje do "a" lub "^"-– tworzy zakres np.[a-z]. Dosłownie trzeba go zapisać jako\-albo umieścić na początku lub końcu np.[-+]lub[+-]
Pozostałe metaznaki, czyli . * + ? $ { } ( ) |, wewnątrz nawiasów oznaczają po prostu siebie.
Dodatkowo Python zgłasza ostrzeżenie FutureWarning przy znaku [ wewnątrz klasy oraz przy podwójnych znakach --, &&, ~~ i ||, ponieważ w przyszłych wersjach mogą one uzyskać specjalne znaczenie. Dlatego je również lepiej escapować.
import re
# ['1', '2', '3']print(re.findall(r'\d', 'Pokój 12B, piętro 3'))# ['P', 'B']print(re.findall(r'[A-Z]', 'Pokój 12B, piętro 3'))# ['Zażółć', 'gęślą', 'jaźń']print(re.findall(r'\w+', 'Zażółć gęślą jaźń!'))W przykładzie:
-
\dznajduje każdą cyfrę osobno, więc liczba 12 zostaje rozbita na "1" i "2" -
[A-Z]dopasowuje tylko wielkie litery, więc z całego tekstu zostają "P" i "B" -
\w+łączy klasę znaków z plusem, który oznacza "jeden lub więcej razy". Dzięki temu wzorzec dopasowuje całe słowa, a nie pojedyncze litery. Wykrzyknik nie jest częścią żadnego słowa, gdyż\wnie obejmuje znaków interpunkcyjnych.
W Pythonie 3 \w domyślnie rozpoznaje litery Unicode, więc polskie znaki są traktowane jak litery. Zakres [a-z] ich jednak nie obejmuje – jeśli potrzebujesz polskich liter, dopisz je: [a-ząćęłńóśźż] dla małych liter lub [a-zA-ZąćęłńóśźżĄĆĘŁŃÓŚŹŻ] dla wszystkich.
Kwantyfikatory - ile razy powtórzyć znak?
Kwantyfikator określa, ile razy może się powtórzyć element, który stoi bezpośrednio przed nim. Tym elementem może być:
- pojedynczy znak np.
a - klasa znaków np.
\dlub[a-z] - grupa w nawiasach okrągłych np.
(ha)+która dopasuje "ha", "haha", "hahaha" itd.
Dzięki kwantyfikatorom nie musisz wielokrotnie powtarzać tego samego symbolu we wzorcu.
| Kwantyfikator | Znaczenie | Przykład | Pasuje do |
|---|---|---|---|
* | 0 lub więcej razy | go*l | gl, gol, goooool |
+ | 1 lub więcej razy | \d+ | 7, 125, 2026 |
? | 0 lub 1 raz (element opcjonalny) | e-?mail | email, e-mail |
{3} | dokładnie 3 razy | \d{3} | 164, 850 |
{2,4} | od 2 do 4 razy | \d{2,4} | 12, 123, 1234 |
{2,} | co najmniej 2 razy | \d{2,} | 12, 12345 |
Dzięki kwantyfikatorom wzorzec na kod pocztowy można zapisać krócej: \d{2}-\d{3}.
Uwaga: w nawiasach klamrowych nie wstawiaj spacji. Zapis {2,4} działa poprawnie, ale {2, 4} Python potraktuje jako zwykły tekst, a nie kwantyfikator.
Różnica między * a +:
+wymaga, aby element wystąpił przynajmniej raz*dopuszcza też całkowity brak elementu. Dlatego wzorzec "go*l" dopasuje nawet samo "gl".
import re
# ['3', '125']print(re.findall(r'\d+', 'Zamówiono 3 laptopy i 125 myszek'))# ['01-164', '05-850']print(re.findall(r'\d{2}-\d{3}', '01-164 Warszawa, 05-850 Ożarów'))# ['color', 'colour'] – "u" jest opcjonalneprint(re.findall(r'colou?r', 'color i colour'))# ['email', 'e-mail']print(re.findall(r'e-?mail', 'Podaj email lub e-mail'))W przykładzie:
-
\d+– wyszukuje ciągi złożone z co najmniej jednej cyfry. Dzięki temu liczby3i125są zwracane w całości, a nie cyfra po cyfrze. -
\d{2}-\d{3}– dopasowuje dokładnie dwie cyfry, myślnik i trzy cyfry, czyli format polskiego kodu pocztowego. Funkcjare.findall()zwraca obydwa kody znalezione w tekście. -
colou?r– znak zapytania odnosi się tylko do litery "u", która stoi bezpośrednio przed nim. Litera ta staje się opcjonalna, więc wzorzec dopasowuje zarówno amerykańską pisownię "color", jak i brytyjską "colour". -
e-?mail– tym razem opcjonalny jest myślnik. Jeden wzorzec obsługuje więc obydwie popularne pisownie: "email" i "e-mail".
Zauważ różnicę: \d znalazłoby każdą cyfrę osobno ('3', '1', '2', '5'), a \d+ – całe liczby, gdyż dopasowuje ciąg cyfr tak długi, jak się da.
Kotwice: początek, koniec, granica słowa
Kotwice to specjalne symbole, które nie dopasowują żadnego znaku, tylko wskazują miejsce w tekście, w którym musi znaleźć się dopasowanie.
Dzięki nim możesz określić, że wzorzec ma wystąpić na początku lub na końcu tekstu albo jako osobne słowo, a nie fragment dłuższego wyrazu.
Najczęściej używane kotwice:
^– początek tekstu (lub linii przy fladzere.MULTILINE)$– koniec tekstu (lub linii)\b– granica słowa, czyli miejsce między znakiem słownym (litera, cyfra, _) a znakiem niesłownym albo początkiem lub końcem tekstu.
import re
# ['kot', 'kot', 'kot']print(re.findall(r'kot', 'kot, kotlet i szkot'))# ['kot'] – tylko osobne słowoprint(re.findall(r'\bkot\b', 'kot, kotlet i szkot'))# True – tekst składa się wyłącznie z cyfrprint(bool(re.search(r'^\d+$', '12345')))# Falseprint(bool(re.search(r'^\d+$', '123a5')))W przykładzie:
-
kot– bez kotwic wzorzec znajduje "kot" wszędzie: jako osobne słowo oraz w "kotlet" i w "szkot" -
\bkot\b– granice słowa wymuszają, aby "kot" był osobnym wyrazem, więc "kotlet" i "szkot" odpadają -
^\d+$– cały tekst, od początku do końca, musi składać się z cyfr. Dlatego "123a5" nie przechodzi -
bool()– funkcjare.search()zwraca obiekt dopasowania alboNone. Funkcjabool()zamienia ten wynik naTruelubFalse, co jest wygodne przy sprawdzaniu poprawności danych
Połączenie ^ i $ to podstawa walidacji danych. Bez kotwic wzorzec \d+ znalazłby cyfry także w tekście "123a5" i sprawdzenie dałoby błędny wynik True
Kotwica
$dopasowuje nie tylko sam koniec tekstu, ale też miejsce tuż przed końcowym znakiem nowej linii. W efekcie wzorzec^\d+$zaakceptuje tekst'12345\n', choć ten zawiera coś więcej niż cyfry. Przy ścisłej walidacji danych lepiej użyć funkcjire.fullmatch(), która sprawdza, czy wzorzec pasuje do całego tekstu, bez żadnych wyjątków:
import re
# True – znak nowej linii został pominiętyprint(bool(re.search(r'^\d+$', '12345\n')))# False – tekst nie składa się wyłącznie z cyfrprint(bool(re.fullmatch(r'\d+', '12345\n')))Przy re.fullmatch() nie trzeba dopisywać kotwic ^ i $ – funkcja sama wymaga dopasowania od początku do końca tekstu.
Alternatywa i grupy w wyrażeniach regularnych
Alternatywa, zapisywana pionową kreską |, działa jak słowo "lub". Pozwala dopasować jeden z kilku wariantów np. "pies|kot" znajdzie w tekście zarówno "pies", jak i "kot".
Grupy tworzy się za pomocą nawiasów okrągłych - łączą kilka elementów wzorca w jedną całość.
Dzięki temu możesz:
- nałożyć kwantyfikator na cały fragment, a nie tylko na ostatni znak np.
(ha)+powtarza "ha", aha+tylko literę "a" - ograniczyć zasięg alternatywy np.
ko(t|ń)dopasuje "kot" lub "koń", podczas gdykot|ńoznaczałoby "kot" albo samo "ń"
Alternatywa i grupy w Pythonie:
a|b– alternatywa - alboa, albob( )– grupa: łączy elementy w całość (np. żeby nałożyć na nie kwantyfikator) i zapamiętuje dopasowany fragment, aby można go było odczytać(?: )– grupa, która tylko łączy elementy, ale niczego nie zapamiętuje.
import re
# ['pies', 'kot']print(re.findall(r'pies|kot', 'pies, kot i chomik'))# ['kot', 'koń']print(re.findall(r'ko(?:t|ń)', 'kot, koń i koza'))# ['hahaha', 'ha'] – "ha" powtórzone wiele razyprint(re.findall(r'(?:ha)+', 'hahaha! ha'))# ['ha', 'ha']print(re.findall(r'(ha)+', 'hahaha! ha'))W przykładzie:
-
pies|kot– wzorzec dopasowuje jedno z dwóch słów, "chomik" nie pasuje do żadnego wariantu -
ko(?:t|ń)– grupa ogranicza alternatywę tylko do ostatniej litery. Wzorzec pasuje do "kot" i "koń", ale nie do "koza" -
(?:ha)+– plus powtarza całą grupę "ha", dlatego wynikiem są całe ciągi "hahaha" i "ha" -
(ha)+– ten sam wzorzec z grupą przechwytującą daje inny wynik. Jeśli wzorzec zawiera grupę przechwytującą,re.findall()zwraca zawartość grupy zamiast całego dopasowania, a przy powtórzeniach – tylko jej ostatnie wystąpienie. Dlatego, gdy grupa służy jedynie do łączenia elementów, warto używać (?: ).
Grupy przechwytujące przydają się, gdy chcesz wyciągnąć z dopasowania konkretne części. Kolejne grupy odczytasz metodą group() z numerem:
import re
wynik = re.search(r'(\d{2})-(\d{3})', 'Kod pocztowy: 05-816')if wynik: print(wynik.group()) # 05-816 – całe dopasowanie print(wynik.group(1)) # 05 – pierwsza grupa print(wynik.group(2)) # 816 – druga grupaDlaczego wzorce zapisujemy jako surowy napis r'...'?
We wzorcach bardzo często pojawia się backslash \. Problem w tym, że w zwykłym napisie Pythona backslash ma też specjalne znaczenie: "\n" to znak nowej linii, a "\b" to znak backspace. Gdyby wzorzec "\bkot\b" zapisać bez r, Python zamieniłby \b na backspace, zanim moduł re w ogóle zobaczyłby wzorzec.
Przedrostek r (ang. raw string) wyłącza tę interpretację – backslash zostaje backslashem.
Dobra praktyka: każdy wzorzec zapisuj jako r'...'.
import re
# [] – nie działa, \b zamienione na backspaceprint(re.findall('\bkot\b', 'kot'))# ['kot']print(re.findall(r'\bkot\b', 'kot'))W przykładzie:
-
'\bkot\b'– bez przedrostkarPython zamienia obydwa\bna znaki backspace. Modułreszuka więc ciągu "backspace, kot, backspace", którego w tekście nie ma, i zwraca pustą listę. -
r'\bkot\b'– dzięki przedrostkowirdo modułuretrafia dokładnie to, co zostało zapisane, więc\bdziała jako granica słowa.
Moduł re – przegląd funkcji
Dotychczas korzystaliśmy głównie z dwóch funkcji modułu re: re.search() i re.findall().
Moduł re oferuje ich jednak więcej. Funkcje różnią się przede wszystkim dwoma rzeczami:
- gdzie szukają dopasowania – w dowolnym miejscu tekstu, tylko od jego początku albo w całym tekście od początku do końca
- co zwracają – pojedynczy obiekt dopasowania, listę znalezionych fragmentów, nowy, zmieniony tekst lub skompilowany wzorzec.
Większość z nich przyjmuje te same podstawowe argumenty: najpierw wzorzec,
potem tekst np. re.search(wzorzec, tekst).
Poniższa tabela zawiera najważniejsze funkcje modułu re:
| Funkcja | Co robi | Zwraca |
|---|---|---|
re.search() | szuka pierwszego dopasowania w dowolnym miejscu tekstu | Match lub None |
re.match() | sprawdza dopasowanie od początku tekstu | Match lub None |
re.fullmatch() | sprawdza, czy cały tekst pasuje do wzorca | Match lub None |
re.findall() | znajduje wszystkie dopasowania | lista napisów (lub krotek, jeśli wzorzec zawiera kilka grup) |
re.finditer() | znajduje wszystkie dopasowania wraz z pozycją | iterator obiektów Match |
re.split() | dzieli tekst w miejscach dopasowań | lista napisów |
re.sub() | zamienia dopasowania na inny tekst | nowy napis |
re.compile() | przygotowuje wzorzec do wielokrotnego użycia | obiekt Pattern |
Funkcja re.search() – wyszukiwanie pierwszego dopasowania
Funkcja re.search() przeszukuje tekst i zwraca pierwsze miejsce, które pasuje do wzorca. Dopasowanie może znajdować się na początku, w środku lub na końcu tekstu. Jeśli funkcja coś znajdzie, zwraca obiekt dopasowania, a jeśli nie – wartość None.
W przykładach wykorzystamy poznane wcześniej elementy:
\w– dowolna litera, cyfra lub podkreślnik_+– jedno lub więcej wystąpień poprzedniego elementu\.– kropka dosłownie.
Przykład 1 - wyszukiwanie skrótu w tekście:
import re
tekst = 'Spotkanie z prof. Kowalskim o godz. 10'wynik = re.search(r'\w+\.', tekst)if wynik: print(wynik.group()) # prof.Wzorzec \w+\. czyta się: "jeden lub więcej znaków słownych (liter, cyfr lub podkreślników), a zaraz po nich kropka". Słowa "Spotkanie" i "z" nie kończą się kropką, więc pierwszym pasującym fragmentem jest prof.. W tekście występuje też skrót godz., ale re.search() kończy działanie na pierwszym dopasowaniu. Aby znaleźć wszystkie skróty, należy użyć funkcji re.findall().
Przykład 2 - wyszukiwanie identyfikatora po stałym prefiksie:
import re
tekst = 'Nowe zgłoszenie id:A7X!! wymaga uwagi'wynik = re.search(r'id:\w\w\w', tekst)if wynik: print('Znaleziono:', wynik.group()) # Znaleziono: id:A7XWzorzec zaczyna się od stałego tekstu id:, po którym muszą wystąpić trzy znaki słowne. Każde \w dopasowuje jeden znak, więc pasują zarówno litery A i X, jak i cyfra 7. Wykrzykniki nie należą do \w, dlatego dopasowanie kończy się na id:A7X.
Ten sam wzorzec można zapisać krócej za pomocą kwantyfikatora: id:\w{3}.
Wzorzec dopasowuje dokładnie trzy znaki słowne, ale nie sprawdza, co występuje po nich. W tekście id:A7X9 również znajdzie id:A7X. Jeśli identyfikator ma mieć dokładnie trzy znaki, dodaj na końcu granicę słowa: id:\w{3}\b.
Przykład 3 - wyszukiwanie adresu e-mail:
Spróbujmy wyciągnąć z tekstu adres e-mail. Na początek użyjemy prostego wzorca \w+@\w+, czyli "znaki słowne, małpa, znaki słowne":
import re
tekst = 'Pytania przyjmuje jan-kowalski@avendi.edu.pl do końca tygodnia'wynik = re.search(r'\w+@\w+', tekst)if wynik: print(wynik.group()) # kowalski@avendiWynik jest niepełny, gdyż klasa \w nie obejmuje myślnika ani kropki. Dopasowanie zaczyna się dopiero po myślniku (kowalski) i kończy przed pierwszą kropką (avendi). Rozwiązaniem jest własna klasa znaków [\w.-], która oznacza "litera, cyfra, podkreślnik, kropka lub myślnik".
import re
tekst = 'Pytania przyjmuje jan-kowalski@avendi.edu.pl do końca tygodnia'wynik = re.search(r'[\w.-]+@[\w.-]+', tekst)if wynik: print(wynik.group()) # jan-kowalski@avendi.edu.plTeraz wzorzec obejmuje cały adres. Zwróć uwagę na zapis klasy znaków:
- kropka wewnątrz nawiasów kwadratowych traci specjalne znaczenie i oznacza zwykłą kropkę, a nie dowolny znak
- myślnik umieszczony na końcu listy jest traktowany dosłownie. Gdyby stał w
środku, np.
[\w-.]Python próbowałby utworzyć z niego zakres, tak jak w "a-z".
Przykład 4 - odczytywanie części adresu e-mail za pomocą grup
Grupy oznaczamy nawiasami okrągłymi ( ). Fragment dopasowany przez grupę można odczytać, podając jej numer w metodzie group(). Grupy numerujemy od 1, a group() lub group(0) zwraca całe dopasowanie.
import re
tekst = 'Pytania prosimy kierować na adres jan-kowalski@avendi.edu.pl do końca tygodnia'wynik = re.search(r'([\w.-]+)@([\w.-]+)', tekst)if wynik: # jan-kowalski@avendi.edu.pl – całe dopasowanie print(wynik.group()) # jan-kowalski – pierwsza grupa (użytkownik) print(wynik.group(1)) # avendi.edu.pl – druga grupa (domena) print(wynik.group(2)) # ('jan-kowalski', 'avendi.edu.pl') – wszystkie grupy naraz print(wynik.groups())Wzorzec jest taki sam, jak w Przykładzie 3, ale obydwie części adresu zostały ujęte w nawiasy. Dzięki temu jednym wyszukiwaniem otrzymujesz zarówno cały adres, jak i jego poszczególne elementy.
Metoda groups() zwraca wszystkie grupy w postaci krotki, co przydaje się, gdy chcesz od razu przypisać je do kilku zmiennych, np. uzytkownik, domena = wynik.groups(). Więcej informacji o krotkach znajdziesz w artykule Krotka w Pythonie.
Obiekt dopasowania przechowuje również informację o tym, w którym miejscu tekstu znaleziono dopasowanie:
import re
tekst = 'Pytania przyjmuje jan-kowalski@avendi.edu.pl do końca tygodnia'wynik = re.search(r'([\w.-]+)@([\w.-]+)', tekst)if wynik: print(wynik.start(), wynik.end()) # 18 44 print(wynik.span()) # (18, 44) print(tekst[18:44]) # jan-kowalski@avendi.edu.plW przykładzie:
start()– zwraca indeks pierwszego znaku dopasowania. Znaki w tekście numerujemy od 0, więc adres zaczyna się od 19. znakuend()– zwraca indeks pozycji tuż za ostatnim znakiem dopasowaniaspan()– zwraca obie wartości naraz w postaci krotki.
Takie indeksy działają tak samo jak przy wycinaniu fragmentów napisu, więc tekst[18:44] zwraca dokładnie jan-kowalski@avendi.edu.pl.
Funkcje re.match() i re.fullmatch() – dopasowanie od początku i do końca tekstu
Funkcja re.match() działa podobnie jak re.search(), ale dopasowanie musi zaczynać się dokładnie od początku tekstu. Nie musi za to sięgać do jego końca. Przypomina operator LIKE 'wzorzec%' w SQL, choć składnia wzorców jest inna.
Funkcja re.match() zwraca:
None– jeśli początek tekstu nie pasuje do wzorca- obiekt dopasowania
re.Match– jeśli dopasowanie istnieje. Tak jak przyre.search(), możesz z niego odczytać dopasowany tekst (metodą.group()) oraz jego pozycję (metodą.span()).
W przykładach użyjemy:
.– jeden dowolny znak,[aeoui]– jedna z liter: a, e, o, u, i.
Przykład 1 - dopasowanie od początku tekstu do wskazanego słowa:
import re
tekst = 'Akademia Avendi, ul. Markerta 23, 02-495 Warszawa'wynik = re.match(r'.*Markerta', tekst)if wynik: # <re.Match object; span=(0, 29), match='Akademia Avendi, ul. Markerta'> print(wynik) # Akademia Avendi, ul. Markerta print(wynik.group())Połączenie .* oznacza "dowolne znaki w dowolnej liczbie". Wzorzec dopasowuje więc cały tekst od jego początku aż do słowa "Markerta" włącznie.
Wywołanie print(wynik) pokazuje, jak wygląda sam obiekt dopasowania: zawiera pozycję (span) i dopasowany fragment (match).
Przykład 2 - wzorzec o stałej długości:
import re
wynik = re.match(r'a...s', 'atlas')if wynik: print(wynik.group()) # atlas print(wynik.span()) # (0, 5)Wzorzec a...s oznacza: litera "a", trzy dowolne znaki i litera "s" – razem dokładnie pięć znaków. Słowo "atlas" spełnia ten warunek od pierwszej do ostatniej litery.
Przykład 3 - dopasowanie początku słowa:
import re
z = re.match(r'pyt.*', 'python')if z: print(z.group()) # python print(z.span()) # (0, 6)
z = re.match(r'.[aeoui]..', 'Bisztynek')if z: print(z.group()) # Bisz print(z.span()) # (0, 4)-
pyt.*– tekst musi zaczynać się od "pyt", a dalej może wystąpić cokolwiek. Dlatego dopasowane zostaje całe słowo "python" -
.[aeoui]..– dowolny znak (B), samogłoska (i) i dwa dowolne znaki (sz). Pozostała część tekstu, czyli "tynek", nie ma znaczenia, gdyżre.match()nie wymaga dopasowania do końca.
Przykład 4 - różnica między re.match() a re.search()
Sprawdzimy, jak zachowa się re.match(), gdy szukany fragment nie stoi na początku tekstu.
import re
tekst = 'Akademia Avendi, ul. Markerta 23, 02-495 Warszawa'print(re.match(r'Warszawa', tekst)) # None# <re.Match object; span=(41, 49), match='Warszawa'>print(re.search(r'Warszawa', tekst))re.match()zwracaNone, gdyż tekst nie zaczyna się od słowa "Warszawa"re.search()przeszukuje cały tekst i znajduje słowo "Warszawa" na końcu adresu.
Przykład 5 - walidacja kodu pocztowego za pomocą re.fullmatch()
Do walidacji danych (np. czy użytkownik wpisał poprawny kod pocztowy) najlepiej użyć funkcji re.fullmatch(). Wymaga ona, aby cały tekst pasował do wzorca, bez żadnych dodatkowych znaków na początku i na końcu:
import re
for kod in ['05-816', '05-8166', 'x05-816', '05816']: ok = re.fullmatch(r'\d{2}-\d{3}', kod) is not None print(kod, ok)Wynik:
05-816 True05-8166 Falsex05-816 False05816 FalseWyrażenie is not None zamienia wynik funkcji na wartość logiczną: True, jeśli dopasowanie istnieje i False, jeśli funkcja zwróciła None.
Podobny efekt co re.fullmatch() da re.match() z kotwicą końca tekstu np. re.match(r'\d{2}-\d{3}$', kod). Jak jednak pokazaliśmy w sekcji o kotwicach, $ dopuszcza końcowy znak nowej linii. Dlatego do ścisłej walidacji bezpieczniejsza jest funkcja re.fullmatch().
Grupy nazwane w wyrażeniach regularnych – (?P<nazwa>...)
Przy wielu grupach numery szybko stają się nieczytelne. Grupie można nadać nazwę za pomocą składni (?P<nazwa>...) i odczytywać ją po nazwie.
Przykład 1 - rozbicie daty na rok, miesiąc i dzień
import re
wzorzec = r'(?P<rok>\d{4})-(?P<miesiac>\d{2})-(?P<dzien>\d{2})'tekst = 'Faktura z dnia 2026-09-21'
z = re.search(wzorzec, tekst)if z: print(z.group('rok')) # 2026 print(z.groupdict()) # {'rok': '2026', 'miesiac': '09', 'dzien': '21'}Każda z trzech grup ma własną nazwę: rok, miesiac i dzien.
- metoda
group('rok')zwraca zawartość jednej wybranej grupy - metoda
groupdict()zwraca w postaci słownika wszystkie grupy nazwane. Kluczami w słowniku są nazwy grup, a wartościami dopasowane fragmenty tekstu.
Przykład 2 - rozkładanie numeru wersji programu na części
Grupy nazwane szczególnie przydają się przy długich, złożonych wzorcach. Dobrym przykładem jest oficjalne wyrażenie regularne ze specyfikacji Semantic Versioning, które rozkłada numer wersji programu na części:
import re
regex = r'^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'napis = '1.0.0-beta'z = re.match(regex, napis)
print(z.groupdict())# {'major': '1',# 'minor': '0',# 'patch': '0',# 'prerelease': 'beta',# 'buildmetadata': None}print(z.groups()) # ('1', '0', '0', 'beta', None)print(z.group('major')) # '1'Jak czytać ten długi wzorzec? Podzielmy go na części:
(?P<major>0|[1-9]\d*)– liczba bez zer wiodących: albo0, albo cyfra 1–9 i dowolnie dużo cyfr\.– kropka dosłownie; to samo powtarza się dlaminoripatch(?:-(?P<prerelease>...))?– opcjonalna część po myślniku, np.-beta(?na końcu grupy oznacza, że cała część może nie wystąpić)(?:\+(?P<buildmetadata>...))?– opcjonalna część po plusie, np.+build.5^i$– całość musi obejmować cały napis.
Grupa, która nie wzięła udziału w dopasowaniu (tu buildmetadata), ma wartość None.
Lista wszystkich dopasowań re.findall()
Funkcja re.findall() przeszukuje cały tekst i zwraca listę wszystkich fragmentów pasujących do wzorca. W przeciwieństwie do re.search() nie zatrzymuje się na pierwszym dopasowaniu.
Jeśli nic nie zostanie znalezione, funkcja zwraca pustą listę, a nie None. Dzięki temu nie trzeba sprawdzać wyniku instrukcją if przed jego użyciem – po pustej liście można np. bezpiecznie przejść pętlą.
Więcej informacji o pętlach znajdziesz w artykule Pętle w Pythonie.
import re
napis = 'Raszyńska 34, 05-816 Michałowice-Osiedle'print(re.findall(r'\d\d-\d\d\d', napis)) # ['05-816']
napis = 'Banderii 4, 01-164 Warszawa, 05-850 Ożarów'print(re.findall(r'\d\d-\d\d\d', napis)) # ['01-164', '05-850']
napis = 'Ożarów'print(re.findall(r'\d\d-\d\d\d', napis)) # []W przykładzie ten sam wzorzec kodu pocztowego zastosowaliśmy do trzech różnych tekstów:
- w pierwszym znajduje się jeden kod, więc lista ma jeden element. Liczba 34 z numeru domu nie pasuje, bo nie ma po niej myślnika
- w drugim są dwa kody i obydwa trafiają do listy w kolejności, w jakiej występują w tekście
- w trzecim nie ma żadnego kodu, dlatego wynikiem jest pusta lista.
Ponieważ wynikiem jest lista, znalezione dopasowania możemy policzyć funkcją len() np. len(re.findall(r'\d{2}-\d{3}', napis)) zwraca liczbę kodów pocztowych w tekście.
Uwaga na grupy: jeśli wzorzec zawiera grupy przechwytujące, re.findall zwraca zawartość grup zamiast całego dopasowania. Przy jednej grupie – listę napisów, przy kilku – listę krotek, w których każdy element odpowiada kolejnej grupie:
import re
tekst = 'kontakt: jan@firma.pl, anna@szkola.edu.pl'# ['jan@firma.pl', 'anna@szkola.edu.pl']print(re.findall(r'[\w.-]+@[\w.-]+', tekst))# [('jan', 'firma.pl'), ('anna', 'szkola.edu.pl')]print(re.findall(r'([\w.-]+)@([\w.-]+)', tekst))W pierwszym wywołaniu wzorzec nie ma grup, więc funkcja zwraca pełne adresy e-mail. W drugim te same adresy zostały podzielone przez dwie grupy (nazwę użytkownika i domenę), dlatego każde dopasowanie trafia do listy jako dwuelementowa krotka.
To bardzo wygodne, gdy chcemy od razu rozbić dane na pola – na przykład wczytać pary "użytkownik–domena" do listy i dalej je przetwarzać.
Informacje na temat list znajdziesz w artykule Lista w Pythonie.
Dopasowania z pozycją re.finditer()
Funkcja re.finditer(), podobnie jak re.findall(), wyszukuje wszystkie dopasowania w tekście. Różni się jednak tym, co zwraca. Zamiast listy napisów otrzymujesz iterator, czyli obiekt, który podaje kolejne wyniki jeden po drugim np. w pętli for. Każdy z tych wyników to pełny obiekt dopasowania (re.Match), taki sam jak przy re.search(). Dzięki temu oprócz samego dopasowanego tekstu znasz też jego pozycję i masz dostęp do grup.
W przykładzie użyjemy wzorca [a-z], który dopasowuje jedną małą literę z zakresu "a–z":
import re
for z in re.finditer(r'[a-z]', 'a1b2c3d4'): print(z.span(), z.group())Wynik:
(0, 1) a(2, 3) b(4, 5) c(6, 7) dPętla for pobiera z iteratora kolejne dopasowania i wyświetla ich pozycję oraz znaleziony znak. Cyfry nie pasują do wzorca, dlatego zostają pominięte.
Metoda span() zwraca indeks początku dopasowania i indeks pozycji tuż za jego końcem – tak samo jak przy wycinaniu napisu, gdzie tekst[0:1] zwraca tylko pierwszy znak.
Funkcję re.finditer() warto wybrać zamiast re.findall() gdy:
- potrzebujesz pozycji dopasowań lub dostępu do grup
- przetwarzasz bardzo długie teksty np. duże pliki z logami – funkcja nie tworzy od razu listy wszystkich wyników, tylko wyszukuje kolejne dopasowania w miarę potrzeby.
Pamiętaj, że iterator można przejść tylko raz. Aby użyć wyników ponownie, wywołaj funkcję jeszcze raz lub zamień wynik na listę funkcją list().
Podział tekstu na fragmenty re.split()
Python ma wbudowaną metodę str.split(), która rozbija tekst na fragmenty po wskazanym separatorze:
# ['red', 'blue', 'green']colors = 'red-blue-green'.split('-')print(colors)
# ['WAW', 'KRK', 'GDN', 'KTW', 'WRO', 'WMI']ports = 'WAW;KRK;GDN;KTW;WRO;WMI'.split(';')print(ports)W przykładzie:
'red-blue-green'.split('-')– tekst zostaje podzielony w każdym miejscu, w którym występuje myślnik. Sam separator nie trafia do wyniku.'WAW;KRK;GDN;KTW;WRO;WMI'.split(';')– separatorem jest średnik, a wynikiem lista kodów lotnisk w kolejności występowania w tekście.
Metoda str.split() wywołana bez argumentu dzieli tekst po białych znakach (spacja, tabulator, nowa linia), traktując ich ciąg jako jeden separator:
fruits = '''applecherryplum banana orange'''.split()# ['apple', 'cherry', 'plum', 'banana', 'orange']print(fruits)Słowa w przykładzie wyżej są rozdzielone znakami nowej linii oraz pojedynczymi i wielokrotnymi spacjami. Mimo tego wynik nie zawiera pustych elementów, gdyż każdy ciąg białych znaków jest traktowany jako jeden separator.
Metoda str.split() nie potrafi jednak użyć kilku różnych separatorów jednocześnie. Tutaj przydaje się re.split(). Separatory wymieniamy w nawiasach kwadratowych [] – zadziała dowolny z nich:
import re
napis = 'abc,def;ghi;jkm-red-blue-green'# ['abc', 'def', 'ghi', 'jkm', 'red', 'blue', 'green']print(re.split(r'[,;-]', napis))Wzorzec [,;-] dopasowuje przecinek, średnik lub myślnik, więc tekst zostaje podzielony w miejscu każdego z tych znaków. Myślnik stoi na końcu nawiasu, dlatego jest traktowany dosłownie, a nie jako zakres znaków.
Jeśli separatory mogą być otoczone spacjami albo powtarzać się, dodaj kwantyfikator:
import re
napis = 'jabłka , gruszki;śliwki ;; wiśnie'# ['jabłka', 'gruszki', 'śliwki', 'wiśnie']print(re.split(r'\s*[,;]+\s*', napis))Wzorzec \s*[,;]+\s* oznacza: "opcjonalne białe znaki, jeden lub więcej przecinków lub średników, opcjonalne białe znaki". Dzięki temu spacje nie trafiają do wyników, a podwójny średnik nie tworzy pustego elementu.
Znajdź i zamień re.sub()
Funkcja re.sub() odnajduje fragmenty pasujące do wzorca i zamienia je na nowy tekst. Domyślnie zamienia wszystkie wystąpienia.
Składnia:
re.sub(wzorzec, zamiennik, tekst, count=0)
- wzorzec – wyrażenie regularne opisujące fragment do zamiany
- zamiennik – tekst, który zostanie wstawiony w miejsce dopasowania lub funkcja zwracająca ten tekst
- tekst – napis, w którym wykonujemy zamianę
- count – maksymalna liczba zamian. Domyślnie (0) zamieniane są wszystkie wystąpienia.
Przykład 1 - zamiana stałego tekstu
import re
src = 'plik_src.md'dst = re.sub(r'_src', '_dst', src)# dst = src.replace('_src', '_dst') # odpowiednik bez wyrażeń regularnychprint(dst) # plik_dst.mdWzorzec _src zostaje znaleziony w nazwie pliku i zamieniony na _dst. Taką zamianę można wykonać także metodą str.replace(), pokazaną w komentarzu. Przy stałym, niezmiennym tekście jest to rozwiązanie prostsze i szybsze. Przewaga re.sub() widoczna jest dopiero przy zamianie fragmentów o zmiennej treści.
Przykład 2 - zamiana fragmentów o zmiennej treści:
import re
name = 'Please give this to Mr. and Mrs. Smith'txt = re.sub(r'\w+\.', '##', name)print(txt) # Please give this to ## and ## SmithWzorzec \w+\. oznacza "jeden lub więcej znaków słownych, a po nich kropka". Mr. i Mrs. pasują do tego wzorca, choć różnią się długością, więc obydwa zostały zamienione na ##.
Metoda str.replace() wymagałaby tutaj osobnej zamiany dla każdego skrótu.
Przykład 3 - przestawianie fragmentów tekstu za pomocą grup
W zamienniku można użyć zawartości grup: \1 to pierwsza grupa, \2 – druga itd. Pozwala to przestawiać fragmenty tekstu:
import re
napis = 'zara ali'txt = re.sub(r'(\w+)\s(\w+)', r'\2, \1', napis)print(txt) # ali, zaraW przykładzie:
-
(\w+)\s(\w+)– dwie grupy przechwytujące, z których każda przechwytuje jedno słowo. Pomiędzy nimi znajduje się jeden biały znak\s -
r'\2, \1'– zamiennik wstawia najpierw drugie słowo, potem przecinek i spację, a na końcu pierwsze słowo.
Zamiennik również zapisujemy jako surowy napis r'...'. Bez przedrostka r Python potraktowałby \1 jako znak specjalny i zamiana nie zadziałałaby poprawnie.
Przykład 4 - zmiana formatu dat
W przykładzie zmieniamy format dat z ISO (RRRR-MM-DD) na polski (DD.MM.RRRR):
import re
tekst = 'Termin: 2026-10-14, zakończenie: 2026-10-15'print(re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3.\2.\1', tekst))W przykładzie:
-
wzorzec zawiera trzy grupy:
rok (\d{4}),miesiąc (\d{2})idzień (\d{2}) -
zamiennik
\3.\2.\1ustawia grupy w odwrotnej kolejności i rozdziela je kropkami. Kropki w zamienniku nie wymagają escapowania, gdyż zamiennik nie jest wzorcem, tylko zwykłym tekstem. -
funkcja zamienia obydwie daty występujące w tekście.
Przykład 5 - czyszczenie wielokrotnych spacji
import re
tekst = 'Za dużo spacji w tekście'print(re.sub(r'\s+', ' ', tekst)) # Za dużo spacji w tekścieWzorzec \s+ dopasowuje ciąg jednego lub większej liczby białych znaków, a każdy taki ciąg zostaje zastąpiony pojedynczą spacją. W ten sam sposób usuniesz nadmiarowe tabulatory i znaki nowej linii, co przydaje się przy czyszczeniu danych tekstowych.
Przykład 6 - funkcja jako zamiennik
Zamiast napisu jako zamiennik można podać funkcję. Otrzymuje ona obiekt Match i zwraca tekst do wstawienia – dzięki temu zamiana może zawierać obliczenia.
import re
def podwyzka(m): cena = float(m.group(1)) return f'{cena * 1.1:.2f} zł'
tekst = 'Kurs: 1000 zł, materiały: 150 zł'# Kurs: 1100.00 zł, materiały: 165.00 złprint(re.sub(r'(\d+(?:\.\d+)?) zł', podwyzka, tekst))W przykładzie:
- podwyższamy wszystkie ceny o 10%
(\d+(?:\.\d+)?) zł– wzorzec dopasowuje liczbę całkowitą lub dziesiętną, po której występuje spacja i "zł". Część dziesiętna jest opcjonalna, a grupa(?: )jedynie łączy elementy, więcgroup(1)zawiera całą liczbę.re.sub()wywołuje funkcjępodwyzka()osobno dla każdego znalezionego dopasowania i przekazuje jej obiektMatch.m.group(1)– odczytuje cenę jako tekst, afloat()zamienia ją na liczbę.f'{cena * 1.1:.2f} zł'– zwraca nową cenę zaokrągloną do dwóch miejsc po przecinku, razem z jednostką.
Funkcja użyta jako zamiennik musi zwracać napis. To on zostanie wstawiony w miejsce dopasowania.
Więcej o definiowaniu własnych funkcji przeczytasz w artykule Tworzenie funkcji w Pythonie.
Zachłanne i leniwe dopasowanie
Różnica między dopasowaniem zachłannym i leniwym to jedna z najczęstszych pułapek przy pracy z wyrażeniami regularnymi.
Kwantyfikatory *, + i ? są domyślnie zachłanne (ang. greedy) – dopasowują jak najwięcej znaków. Dopisanie ? za kwantyfikatorem (*?, +?, ??) zmienia je w leniwe (ang. lazy) – dopasowują jak najmniej.
Przykład 1 - wyszukiwanie znaczników HTML
import re
html = '<b>pogrubiony</b> i <i>kursywa</i>'print(re.findall(r'<.*>', html)) # ['<b>pogrubiony</b> i <i>kursywa</i>']print(re.findall(r'<.*?>', html)) # ['<b>', '</b>', '<i>', '</i>']W przykładzie:
-
<.*>wzorzec zachłanny – po znalezieniu pierwszego znaku<wzorzec pobiera wszystkie znaki aż do końca linii, a następnie cofa się do ostatniego znaku>w tekście. W efekcie otrzymujemy jeden długi fragment obejmujący wszystkie znaczniki i tekst pomiędzy nimi. -
<.*?>wzorzec leniwy - po znaku<wzorzec pobiera znaki po jednym i zatrzymuje się na pierwszym napotkanym>. Wynik to osobne znaczniki.
Przykład 2 - odczytywanie wartości w cudzysłowach
import re
tekst = 'imie="Jan" nazwisko="Kowalski"'print(re.findall(r'"(.*)"', tekst)) # ['Jan" nazwisko="Kowalski']print(re.findall(r'"(.*?)"', tekst)) # ['Jan', 'Kowalski']W przykładzie:
-
"(.*)"– zachłanna grupa zaczyna się od pierwszego cudzysłowu i kończy na ostatnim. Wynik obejmuje więc obydwie wartości wraz z fragmentemnazwisko=pomiędzy nimi. -
"(.*?)"– leniwa grupa kończy się na najbliższym cudzysłowie zamykającym, dlatego każda wartość zostaje odczytana osobno.
Wzorce zawierają grupę przechwytującą, więc re.findall() zwraca tylko zawartość grupy, czyli tekst bez cudzysłowów.
Alternatywą dla leniwego kwantyfikatora jest zanegowana klasa znaków: "([^"]*)", czyli "cudzysłów, dowolne znaki oprócz cudzysłowu, cudzysłów".
W tym przykładzie daje taki sam wynik, a często działa szybciej, gdyż wzorzec nie musi sprawdzać po każdym znaku, czy dopasowanie można już zakończyć. Taki zapis jest też bardziej jednoznaczny, gdyż wprost określa, na jakim znaku dopasowanie ma się zatrzymać.
Flagi: wielkość liter, wiele linii, komentarze
Flagi w wyrażeniach regularnych zmieniają sposób działania wzorca np. pozwalają ignorować wielkość liter albo wyszukiwać dopasowania w każdej linii tekstu osobno. Podajemy je w parametrze flags.
| Flaga | Skrót | Działanie |
|---|---|---|
re.IGNORECASE | re.I | ignoruje wielkość liter |
re.MULTILINE | re.M | ^ i $ oznaczają początek i koniec każdej linii |
re.DOTALL | re.S | . dopasowuje także znak nowej linii \n |
re.VERBOSE | re.X | pozwala pisać wzorzec w wielu liniach, ze spacjami i komentarzami |
Przykład 1 - ignorowanie wielkości liter
import re
# ['python']print(re.findall(r'python', 'Python, PYTHON, python'))# ['Python', 'PYTHON', 'python']print(re.findall(r'python', 'Python, PYTHON, python', flags=re.IGNORECASE))Bez flagi wzorzec python dopasowuje tylko wyraz zapisany małymi literami. Po dodaniu flagi re.IGNORECASE wielkość liter przestaje mieć znaczenie, więc znalezione zostają wszystkie trzy warianty.
Przykład 2 - wyszukiwanie nagłówków Markdown w wielu liniach
import re
md = '''# TytułTekst wstępu## Rozdział 1Treść## Rozdział 2'''
# ['Tytuł', 'Rozdział 1', 'Rozdział 2']print(re.findall(r'^#+ (.*)$', md, flags=re.M))W przykładzie:
-
^– dzięki fladzere.Moznacza początek każdej linii, a nie tylko początek całego tekstu, -
#+– jeden lub więcej znaków#, czyli nagłówek dowolnego poziomu -
(.*)– grupa przechwytująca treść nagłówka, dlatego w wyniku nie ma znaków# -
$– dzięki fladzere.Moznacza koniec każdej linii.
Bez flagi re.M znaki ^ i $ oznaczałyby początek i koniec całego tekstu. Użycie re.M powoduje, że znaki ^ i $ oznaczają początek i koniec pojedynczych linijek.
Przykład 3 - czytelny zapis wzorca z flagą re.VERBOSE
Flaga re.VERBOSE poprawia czytelność długich wzorców – spacje we wzorcu są ignorowane, a po znaku # można pisać komentarze.
import re
wzorzec = re.compile(r''' (?P<kierunkowy>\+48)? # opcjonalny numer kierunkowy \s? # opcjonalna spacja (?P<numer>\d{3}[\s-]?\d{3}[\s-]?\d{3}) # 9 cyfr, grupy po 3''', re.VERBOSE)
for tel in ['+48 600 100 200', '600-100-200', '600100200']: z = wzorzec.fullmatch(tel) print(tel, '->', z.group('numer') if z else 'niepoprawny')Wynik:
+48 600 100 200 -> 600 100 200600-100-200 -> 600-100-200600100200 -> 600100200W przykładzie:
-
(?P<kierunkowy>\+48)?– opcjonalny numer kierunkowy. Plus jest escapowany, gdyż bez backslasha byłby kwantyfikatorem -
\s?– opcjonalna spacja. W trybiere.VERBOSEzwykła spacja we wzorcu jest ignorowana, dlatego biały znak zapisujemy jako\s -
(?P<numer>...)– dziewięć cyfr w trzech blokach, rozdzielonych opcjonalnie spacją lub myślnikiem -
wzorzec.fullmatch(tel)– sprawdza, czy cały numer pasuje do wzorca, az.group ('numer')zwraca sam numer, bez kierunkowego.
Flagi można łączyć operatorem |:
re.findall(pattern, string, flags=re.I | re.M | re.X)Wzorzec działa wtedy ze wszystkimi wskazanymi ustawieniami jednocześnie: ignoruje wielkość liter, traktuje ^ i $ jako początek i koniec każdej linii oraz może zawierać spacje i komentarze.
Kompilacja wyrażeń re.compile()
Funkcja re.compile() przygotowuje wyrażenie regularne do wielokrotnego użycia.
Zanim wzorzec zostanie użyty, Python musi go przeanalizować i przetłumaczyć na wewnętrzną postać. Funkcja re.compile() robi to raz i zwraca obiekt Pattern, który ma te same metody, co moduł re: search(), match(), fullmatch(), findall(), finditer(), split(), sub().
import re
name = 'Please give this to Mrs. Smith'rec = re.compile(r'\w+\.')z = rec.search(name)if z: print(z.group()) # Mrs.W przykładzie:
-
re.compile(r'\w+\.')– tworzy skompilowany wzorzec "słowo zakończone kropką" i zapisuje go w zmiennejrec -
rec.search(name)– wywołuje metodęsearch()bezpośrednio na skompilowanym wzorcu. Podajemy wtedy tylko tekst, bo wzorzec jest już zapisany w obiekcie -
z.group()– zwraca znaleziony fragment, czyli skrót "Mrs."
Wynik jest taki sam jak przy wywołaniu re.search(r'\w+\.', name). Różnica polega tylko na tym, że wzorzec został przygotowany wcześniej i można go użyć wiele razy.
Kiedy warto kompilować wyrażenia regularne?
- gdy ten sam wzorzec jest używany wiele razy np. w pętli po liniach dużego pliku
- gdy chcemy nadać wzorcowi czytelną nazwę (np.
KOD_POCZTOWY = re.compile(r'\d{2}-\d {3}')) i używać go w wielu miejscach programu. - gdy wzorzec ma stałe flagi np.
re.IGNORECASE. Podajemy je raz, przy kompilacji, i nie trzeba ich powtarzać przy każdym wywołaniu.
Moduł re przechowuje w pamięci podręcznej ostatnio używane wzorce, więc zysk wydajności bywa niewielki – ale kod z nazwanymi, skompilowanymi wzorcami jest czytelniejszy.
Gotowe wzorce do typowych zadań
Poniżej znajdziesz zestaw gotowych wyrażeń regularnych do najczęstszych zadań, takich jak wyszukiwanie kodów pocztowych, dat, adresów e-mail czy numerów telefonów.
Wzorce są celowo uproszczone – dobrze sprawdzają się w codziennej pracy, ale nie pokrywają wszystkich przypadków brzegowych.
| Zadanie | Wzorzec | Przykład dopasowania |
|---|---|---|
| kod pocztowy | \d{2}-\d{3} | 05-816 |
| liczba całkowita | -?\d+ | -42 |
| liczba dziesiętna (kropka lub przecinek) | -?\d+(?:[.,]\d+)? | 3,14 |
| data ISO | \d{4}-\d{2}-\d{2} | 2026-09-21 |
| data polska | \d{2}\.\d{2}\.\d{4} | 21.09.2026 |
| godzina | (?:[01]\d|2[0-3]):[0-5]\d | 14:05 |
| adres e-mail (uproszczony) | [\w.+-]+@[\w-]+\.[\w.-]+ | jan.kowalski@firma.pl |
| telefon komórkowy PL | (?:\+48)?\s?\d{3}[\s-]?\d{3}[\s-]?\d{3} | +48 600 100 200 |
| adres URL | https?://[^\s]+ | https://avendi.edu.pl |
| hashtag | #\w+ | #python |
| wiele spacji | \s{2,} | " " |
Wzorce z tabeli można wykorzystać na dwa sposoby:
- do wyszukiwania danych w dłuższym tekście – z funkcją
re.findall()lubre.search() - do walidacji danych np. wpisanych przez użytkownika – z funkcją
re.fullmatch(), która sprawdza, czy cały tekst pasuje do wzorca.
Wyjaśnienie niektórych wzorców:
-
Liczba dziesiętna – część po separatorze jest ujęta w grupę
(?: )z kwantyfikatorem?, dzięki czemu jest opcjonalna. Wzorzec pasuje zarówno do42, jak i do3,14lub3.14. Grupa nie zapamiętuje dopasowania, więcre.findall()zwraca całe liczby, a nie tylko ich część dziesiętną. -
Godzina – alternatywa
|obsługuje dwa przypadki: godziny od 00 do 19 ([01]\d) oraz od 20 do 23 (2[0-3]). Minuty[0-5]\dprzyjmują wartości od 00 do 59, dlatego zapis25:70nie zostanie dopasowany. -
Telefon komórkowy PL – numer kierunkowy
+48oraz spacje lub myślniki między blokami cyfr są opcjonalne, dlatego wzorzec rozpoznaje zapisy+48 600 100 200,600-100-200i600100200. -
Wiele spacji – kwantyfikator
{2,}oznacza co najmniej dwa wystąpienia, więc pojedyncze spacje między słowami nie są dopasowywane.
Przykład – wyciągnięcie wszystkich adresów URL z tekstu:
import re
tekst = ('Dokumentacja: https://docs.python.org/3/library/re.html, ' 'kursy: https://avendi.edu.pl/szkolenia/python/')# ['https://docs.python.org/3/library/re.html',#'https://avendi.edu.pl/szkolenia/python/']print(re.findall(r'https?://[^\s,]+', tekst))W przykładzie:
-
https?– literasjest opcjonalna, więc pasuje zarównohttp, jak ihttps -
://– dosłowny fragment występujący w każdym adresie URL -
[^\s,]+– jeden lub więcej dowolnych znaków oprócz białych znaków i przecinka. Dopasowanie kończy się więc na spacji lub przecinku, który w tekście oddziela adresy od dalszej treści.
W porównaniu ze wzorcem z tabeli dodaliśmy do klasy znaków przecinek. Bez tej zmiany pierwszy adres zostałby dopasowany razem z przecinkiem, czyli jako https://docs.python.org/3/library/re.html,. To dobry przykład tego, że gotowy wzorzec często trzeba dostosować do konkretnych danych.
Najczęstsze błędy
Większość problemów z wyrażeniami regularnymi w Pythonie wynika z kilku powtarzających się pomyłek. Wzorzec może wyglądać poprawnie, a mimo tego nie znajdować dopasowań, zwracać za dużo tekstu albo przepuszczać niepoprawne dane.
Poniżej zebraliśmy najczęstsze błędy wraz ze wskazówkami, jak ich uniknąć.
-
Brak
rprzed wzorcem -'\bkot\b'nie działa, gdyż\bzostaje zamienione przez Pythona na znak backspace. Wzorce zapisuj zawsze jako surowy napis np.r'\bkot\b'. -
Odczyt wyniku bez sprawdzenia - wywołanie
re.search(...).group()zgłosi wyjątekAttributeError: 'NoneType' object has no attribute 'group', jeśli nic nie znaleziono. Przed odczytaniem wyniku sprawdź go warunkiemif z:. -
Pomylenie
matchzsearch- funkcjare.match()szuka dopasowania tylko od początku tekstu. Jeśli szukany fragment może znajdować się w środku – użyjre.search(). -
Walidacja bez kotwic - wywołanie
re.search(r'\d{2}-\d{3}', 'abc 05-816 xyz')zwróci dopasowanie, choć cały tekst nie jest kodem pocztowym. Do walidacji danych używajre.fullmatch(). -
Niezamierzona zachłanność - kwantyfikator
.*dopasowuje jak najwięcej znaków. Jeśli wynik jest dłuższy niż się spodziewasz, użyj leniwego kwantyfikatora.*?lub zanegowanej klasy znaków np.[^"]*. -
Nieescapowany znak specjalny - kropka we wzorcu
plik.txtoznacza dowolny znak, więc wzorzec pasuje także doplikXtxt. Użyj zapisuplik\.txtalbo funkcjire.escape('plik.txt'), która automatycznie doda backslash przed wszystkimi znakami specjalnymi:import reprint(re.escape('cena (brutto): 9.99$')) # cena\ \(brutto\):\ 9\.99\$ -
Polskie litery w zakresie "[a-z]" - zakres
[a-z]nie obejmujeą,ć,ęitd. Użyj klasy\wlub dopisz polskie znaki do klasy np.[a-ząćęłńóśźż].
Ściągawka (cheatsheet)
Poniższa ściągawka zbiera w jednym miejscu najważniejsze elementy składni wyrażeń regularnych w Pythonie: klasy znaków, kwantyfikatory, kotwice, grupy i flagi.
W lewej kolumnie znajdziesz podstawowy symbol, a w prawej jego odwrotność lub wariant np. leniwą wersję kwantyfikatora.
| kod | alternatywny kod |
|---|---|
| \d=cyfra | \D=nie cyfra |
| \w=litera,cyfra,_ | \W=nie litera,cyfra,_ |
| \n=nowa linia | .=dowolny znak (oprócz \n) |
| \s=spacja,tabulator,nowa linia | \S= nie spacja, nie tabulator, nie nowa linia |
| ^=początek tekstu | $=koniec tekstu |
| *=0 lub więcej (greedy) | *?=0 lub więcej (lazy) |
| +=1 lub więcej (greedy) | +?=1 lub więcej (lazy) |
| ? = 0 lub 1 (greedy) | ?? = 0 lub 1 (lazy) |
| {m}=dokładnie m razy | {m,n}=między m i n razy |
| \b=granica słowa | \B=nie granica słowa |
| [abc]=jedna z liter: a,b lub c | [^abc]=dowolny znak oprócz: a,b oraz c |
| [a-z]=dowolna litera z zakresu włącznie a i z | [^a-z]=dowolny znak nie z zakresu |
| a|b=albo a, albo b | \\=jeden znak backslash \ |
| (xxx)=grupa zapamiętująca (capture) | (?:xxx)=grupa bez zapamiętywania |
| (?P<nazwa>xxx)=grupa nazwana | \1, \g<nazwa>=odwołanie do grupy w re.sub() |
Flagi:
re.IGNORECASEignoruj wielkość liter ; skrót:re.Ire.MULTILINEznak ^=początek linii, $=koniec linii ; skrót:re.Mre.DOTALLznak . = dowolny znak (także \n) ; skrót:re.Sre.VERBOSEspacje i komentarze#we wzorcu są ignorowane ; skrót:re.X
Przykładowe użycie flag:
re.findall(pattern, string, flags=re.I | re.M | re.X)FAQ – najczęściej zadawane pytania
Poniżej znajdziesz krótkie odpowiedzi na pytania, które najczęściej pojawiają się przy nauce wyrażeń regularnych w Pythonie.
Czy moduł re trzeba instalować?
Nie. Moduł re jest częścią biblioteki standardowej Pythona, więc wystarczy go zaimportować poleceniem import re.
Jaka jest różnica między re.search() a re.match()?
Funkcja re.search() szuka dopasowania w dowolnym miejscu tekstu, a re.match() tylko od jego początku. Do sprawdzenia, czy cały tekst pasuje do wzorca, służy funkcja re.fullmatch().
Jaka jest różnica między re.findall() a re.finditer()?
Funkcja re.findall() zwraca od razu listę znalezionych napisów (lub krotek, jeśli wzorzec zawiera kilka grup). Funkcja re.finditer() zwraca iterator obiektów Match, z których można odczytać także pozycję dopasowania i grupy. Wyniki są wyszukiwane po kolei, bez tworzenia całej listy w pamięci, co przydaje się przy dużych plikach.
Jak zamienić tekst za pomocą wyrażenia regularnego?
Do zamiany tekstu służy funkcja re.sub(wzorzec, zamiennik, tekst). Zamienia ona wszystkie fragmenty pasujące do wzorca, a w zamienniku można używać zawartości grup (\1, \2) lub podać funkcję, która obliczy nowy tekst.
Jak wyszukać znak specjalny np. kropkę lub nawias?
Poprzedź go backslashem, np. \. lub \(, albo przepuść cały tekst przez funkcję re.escape(), która automatycznie doda backslash przed wszystkimi znakami specjalnymi.
Jak zignorować wielkość liter?
Dodaj flagę re.IGNORECASE (w skrócie re.I) np. re.findall(r'python', tekst, flags=re.I). Wzorzec dopasuje wtedy zarówno "python", jak i "Python" czy "PYTHON".
Czy wyrażenie regularne nadaje się do walidacji adresu e-mail?
W uproszczonej postaci tak, do wstępnego sprawdzenia formatu adresu. Pełna specyfikacja adresu e-mail jest bardzo złożona, dlatego ostateczną weryfikacją jest zawsze wysłanie wiadomości z linkiem potwierdzającym.
Jak testować wyrażenia regularne?
Najwygodniej w interaktywnej konsoli Pythona lub w edytorze kodu np. VS Code czy PyCharm. Obydwa edytory obsługują wyszukiwanie z użyciem wyrażeń regularnych.
Pomocne są także serwisy online np. regex101.com - należy wybrać wariant Python.
Podsumowanie
Wyrażenia regularne to jedno z najbardziej uniwersalnych narzędzi do pracy z tekstem. Pozwalają wyszukiwać, sprawdzać, dzielić i zamieniać fragmenty tekstu na podstawie ich budowy, a nie konkretnej treści.
W Pythonie obsługuje je wbudowany moduł re, a najważniejsze funkcje to:
re.search()– znajdź pierwsze dopasowanie w dowolnym miejscure.match()/re.fullmatch()– sprawdź początek / cały tekstre.findall()/re.finditer()– znajdź wszystkie dopasowaniare.split()– podziel tekst po wielu separatorachre.sub()– zamień fragmenty tekstu, także z użyciem grup i funkcjire.compile()– przygotuj wzorzec do wielokrotnego użycia.
Ucząc się wyrażeń regularnych, zacznij od prostych wzorców (\d+, \w+, [abc]), stopniowo dodawaj grupy i kwantyfikatory, a długie wzorce zapisuj z flagą re.VERBOSE i komentarzami. Pamiętaj o surowych napisach r'...' i o różnicy między dopasowaniem zachłannym i leniwym.
Praktyczne zastosowanie re.search() i re.sub() pokazujemy w artykule Konwersja obrazków PNG do WebP w Pythonie.
Jeśli swobodnie czujesz się w podstawach Pythona i chcesz przećwiczyć bardziej zaawansowane tematy, zobacz nasz kurs Python poziom zaawansowany.
