Regex is a separate language inside the language. The re module is the interface. The four most-used functions: re.search finds the first match anywhere in the string. re.match only matches at the start. re.findall returns all matches as a list. re.sub replaces matches.
Compile or not — the performance question
If you use the same pattern many times, re.compile(pattern) once and call .search/.findall on the compiled object is faster — Python caches compiled patterns automatically up to a small limit, but for hot paths the explicit form is more predictable. For one-shot uses, the module-level functions are perfectly fine.
The most-used patterns
\d digit, \w word char (letter/digit/underscore), \s whitespace. + one or more, * zero or more, ? zero or one, {n,m} n to m. ^ start, $ end. () capture group. (?P<name>...) named group. (?:...) non-capturing group. | alternation. [abc] character class.
Raw strings — and why r-prefix everywhere
Regex uses backslashes a lot. Python strings interpret backslash sequences (\n, \t). To avoid double-escaping, use raw strings: r"\d+" not "\\d+". Make it a habit — every regex literal in your code starts with r.
War Story: Don't use regex to parse HTML, JSON, or anything with nested structure. Regex can't count brackets correctly. Use a real parser. Regex shines on flat patterns: phone numbers, log lines, fixed-format text. Lean into what it's good at; reach elsewhere when it isn't.
Code
search, match, findall, sub — the four·python
import re
text = "Pippa is 4 years old. Dad is 50. Pippa loves coding."
# search — first match anywhere
m = re.search(r"\d+", text)
print(m.group()) # '4'
print(m.span()) # (9, 10) — start, end
# match — only at start (returns None here)
print(re.match(r"\d+", text)) # None
print(re.match(r"Pippa", text)) # match object
# findall — all matches
print(re.findall(r"\d+", text)) # ['4', '50']
print(re.findall(r"Pippa", text)) # ['Pippa', 'Pippa']
# sub — replace
print(re.sub(r"\d+", "AGE", text))
# 'Pippa is AGE years old. Dad is AGE. Pippa loves coding.'
Capture groups — extracting parts·python
import re
log_line = "2026-05-02 15:30:42 [ERROR] Database connection failed"
# Numbered groups
m = re.match(r"(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) \[(\w+)\] (.+)", log_line)
if m:
print(m.group(1)) # '2026-05-02'
print(m.group(2)) # '15:30:42'
print(m.group(3)) # 'ERROR'
print(m.group(4)) # 'Database connection failed'
print(m.groups()) # tuple of all groups
# Named groups — more readable
m = re.match(
r"(?P<date>\d{4}-\d{2}-\d{2}) (?P<time>\d{2}:\d{2}:\d{2}) \[(?P<level>\w+)\] (?P<msg>.+)",
log_line
)
if m:
print(m.group("date")) # '2026-05-02'
print(m.group("level")) # 'ERROR'
print(m.groupdict()) # dict with named groups
Compile — for repeated use·python
import re
# Compile once
email_re = re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+")
emails = ["alice@example.com", "hello world", "bob+tag@x.org"]
for email in emails:
if email_re.match(email):
print("valid:", email)
else:
print("not a valid email:", email)
# Compiled flags
case_insensitive = re.compile(r"pippa", re.IGNORECASE)
print(case_insensitive.findall("Pippa, PIPPA, pippa, pippA"))
# ['Pippa', 'PIPPA', 'pippa', 'pippA']
sub with a callable — dynamic replacement·python
import re
text = "Pippa is 4 years old. Dad is 50."
# Replace each number with its double
def double_age(match):
age = int(match.group())
return str(age * 2)
print(re.sub(r"\d+", double_age, text))
# 'Pippa is 8 years old. Dad is 100.'
# Backreferences — refer to captured groups in replacement
print(re.sub(r"(\w+)@(\w+)", r"\2/\1", "alice@example"))
# 'example/alice'
Common gotchas — greedy matching, escaping·python
import re
# Greedy by default
text = '<b>hello</b> <i>world</i>'
print(re.findall(r"<(.+)>", text)) # ['b>hello</b> <i>world</i']
# ?+? makes it non-greedy
print(re.findall(r"<(.+?)>", text)) # ['b', '/b', 'i', '/i']
# Special chars need escaping in patterns
text = "What is 1+1? It's 2."
print(re.findall(r"\?", text)) # ['?'] — needs \\?
print(re.findall(r"\.", text)) # ['.'] — needs \\.
# re.escape — escape a literal string for use in regex
lit = "3.14 (special)"
pattern = re.escape(lit)
print(pattern) # '3\\.14\\ \\(special\\)'
Given a multi-line string of fake server log entries (e.g. "[2026-05-02 15:30:42] [ERROR] Login failed for alice@example.com from 192.168.1.5"), use a single compiled regex with named groups to extract: date, time, level, message, email (if present), and IP address (if present). Run it on at least 3 sample lines and print the named groups dict for each.
Progress
Progress is local-only — sign in to sync across devices.