Python RegEx
A regular expression (regex) is a small pattern language for finding and extracting text: every email address in a page, every number in a log, every date in a report. Python’s re module runs these patterns for you.
- search, match, fullmatch, findall and sub
- Character classes and quantifiers
- Capture groups and named groups
- Flags and compiled patterns
Your first regex
Write patterns as raw strings (r"...") so backslashes reach the regex engine unchanged.
import re text = "Order 4521 shipped on 2026-11-02, order 4522 pending"match = re.search(r"\d+", text) # first run of digitsprint(match.group(), match.start(), match.end()) print(re.findall(r"\d+", text)) # every run of digits4521 6 10 ['4521', '2026', '11', '02', '4522']
The main functions
| Function | Returns |
|---|---|
re.search(p, s) | First match anywhere, or None |
re.match(p, s) | Match only at the start, or None |
re.fullmatch(p, s) | Match only if the whole string fits |
re.findall(p, s) | List of all matches (or of groups) |
re.finditer(p, s) | Iterator of match objects |
re.sub(p, repl, s) | String with matches replaced |
re.split(p, s) | List split on the pattern |
import re print(re.match(r"\d+", "123abc"))print(re.match(r"\d+", "abc123"))print(bool(re.fullmatch(r"\d{6}", "411001")), bool(re.fullmatch(r"\d{6}", "41100")))print(re.sub(r"\s+", " ", "too many spaces"))print(re.split(r"[,;]\s*", "a, b;c ,d"))<re.Match object; span=(0, 3), match='123'> None True False too many spaces ['a', 'b', 'c ', 'd']
Always check for None before calling .group() on the result of search or match: if m := re.search(p, s): print(m.group()).
Character classes
| Pattern | Matches |
|---|---|
\d / \D | a digit / not a digit |
\w / \W | a word character (letter, digit, _) / not |
\s / \S | whitespace / not |
. | any character except newline |
[aeiou] | one of the listed characters |
[a-z0-9] | a character in the ranges |
[^0-9] | any character NOT listed |
import re s = "Room B-12, floor 3; call +91 98765 43210"print(re.findall(r"[A-Z]", s))print(re.findall(r"[aeiou]", s))print(re.findall(r"[^\w\s]", s))['R', 'B'] ['o', 'o', 'o', 'o', 'a'] ['-', ',', ';', '+']
Quantifiers
| Pattern | Meaning |
|---|---|
* | 0 or more |
+ | 1 or more |
? | 0 or 1 (optional) |
{3} | exactly 3 |
{2,4} | 2 to 4 |
import re print(re.findall(r"colou?r", "color colour colr"))print(re.findall(r"\d{2,3}", "1 12 123 1234"))print(re.findall(r"go+gle", "ggle gogle google gooogle"))['color', 'colour'] ['12', '123', '123'] ['gogle', 'google', 'gooogle']
Quantifiers are greedy: they take as much as possible. Add ? to make them lazy:
import re html = "<b>bold</b> and <i>italic</i>"print(re.findall(r"<.+>", html)) # greedy: one huge matchprint(re.findall(r"<.+?>", html)) # lazy: each tag['<b>bold</b> and <i>italic</i>'] ['<b>', '</b>', '<i>', '</i>']
Anchors and boundaries
^ is the start, $ the end, and \b a word boundary:
import re print(re.findall(r"\bcat\b", "cat catalog concat cat."))print(bool(re.search(r"^ERROR", "ERROR: disk full")), bool(re.search(r"\.py$", "test_login.py")))['cat', 'cat'] True True
Groups
Brackets capture part of the match. findall returns the groups when the pattern has them.
import re log = "2026-11-02 ERROR db; 2026-11-03 WARN cache"for m in re.finditer(r"(\d{4})-(\d{2})-(\d{2}) (\w+)", log): print(m.group(0), "->", m.group(1), m.group(4)) print(re.findall(r"(\d{4})-\d{2}-\d{2} (\w+)", log))2026-11-02 ERROR -> 2026 ERROR
2026-11-03 WARN -> 2026 WARN
[('2026', 'ERROR'), ('2026', 'WARN')]Named groups make patterns self-documenting:
import re m = re.search(r"(?P<user>[\w.]+)@(?P<domain>[\w.-]+)", "contact: asha.k@sqatools.in today")print(m.group("user"), m.group("domain"))print(m.groupdict())asha.k sqatools.in
{'user': 'asha.k', 'domain': 'sqatools.in'}Use groups in a replacement with \1 or \g<name>:
import reprint(re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", "Due 2026-11-02 and 2026-12-25"))Due 02/11/2026 and 25/12/2026
Flags
import re text = "Python\npython\nPYTHON"print(re.findall(r"python", text, flags=re.IGNORECASE))print(re.findall(r"^p\w+", text, flags=re.I | re.M)) # ^ at every line start['Python', 'python', 'PYTHON'] ['Python', 'python', 'PYTHON']
Compile reusable patterns
import re EMAIL = re.compile(r"^[\w.+-]+@[\w-]+(\.[\w-]+)+$")for e in ["asha@sqatools.in", "bad@", "ravi.k+test@mail.co.uk", "no spaces@x.com"]: print(e, bool(EMAIL.match(e)))asha@sqatools.in True bad@ False ravi.k+test@mail.co.uk True no spaces@x.com False
Regex is great for patterns in text, but do not use it to parse HTML or JSON. Use html.parser, BeautifulSoup or json instead.
Escape special characters
Characters like . * + ? ( ) [ ] { } ^ $ | and the backslash have special meanings. Escape them with a backslash, or let re.escape() do it:
import re price = "Rs. 1,499.50 (incl. tax)"print(re.findall(r"\d[\d,]*\.\d+", price))print(re.escape("1+1=2?"))['1,499.50'] 1\+1=2\?
Check your understanding
Predict the output
import res = "a1b22c333"print(re.findall(r"\d", s), re.findall(r"\d+", s), re.sub(r"\d+", "#", s))['1', '2', '2', '3', '3', '3'] ['1', '22', '333'] a#b#c#
\d matches one digit at a time, \d+ matches each run of digits, and sub replaces each run with #.
Quick quiz
1. What is the difference between re.match and re.search?
Use search unless you specifically need the start.
2. What does \d{3} match?
{3} repeats the previous item exactly three times.
3. Why write patterns as raw strings like r"\d+"?
Without r, "\b" would be a backspace character instead of a word boundary.
4. What does re.findall return when the pattern has one group?
With groups, findall returns the captured parts.
Exercise: extract phone numbers
Find all 10-digit Indian mobile numbers (starting with 6, 7, 8 or 9) in the text, allowing an optional +91 and a space or hyphen after the first 5 digits.
"Call 98765-43210 or +91 91234 56789; old: 12345 67890"
98765-43210 -> 9876543210 +91 91234 56789 -> 9123456789
Hint 1
Start with (?:\+91\s?)? for the optional country code; (?: ) groups without capturing.
Hint 2
Then [6-9]\d{4}[\s-]?\d{5}.
import re text = "Call 98765-43210 or +91 91234 56789; old: 12345 67890"pattern = r"(?:\+91\s?)?[6-9]\d{4}[\s-]?\d{5}"for number in re.findall(pattern, text): digits = re.sub(r"\D", "", number)[-10:] print(number, "->", digits)98765-43210 -> 9876543210 +91 91234 56789 -> 9123456789
The pattern requires the first digit to be 6-9, so “12345 67890” is skipped. re.sub(r"\D", "", ...) removes everything that is not a digit, and [-10:] drops the 91 prefix.
Program Practice
20 Python RegEx Programs
Every program has a step-by-step explanation, colour-coded code and its real output.
Python regex FAQ
What is the re module in Python?
re is the standard-library module for regular expressions. It provides search, match, findall, sub, split and compile.
How do I extract numbers from a string in Python?
re.findall(r"\d+", text) returns every run of digits as strings. Use r"-?\d+(?:\.\d+)?" to include negatives and decimals.
What does re.compile do?
It turns a pattern into a reusable object. It makes code clearer when the same pattern is used many times; Python also caches recently used patterns automatically.
How do I make a regex case-insensitive?
Pass flags=re.IGNORECASE (or re.I), or start the pattern with (?i).