C.W.K.
Stream
Lesson 04 of 06 · published

re — Regular Expressions in Python

~22 min · re, regex, match, search, compile

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

The re module — Python's regex interface

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\\)'

External links

Exercise

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.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.