Lesson 7 of 10
A regular expression describes a pattern of text. Use them to find, validate, extract, and replace when plain string methods aren't enough.
import re
text = "Contact: ada@example.com or grace@test.org"
emails = re.findall(r"\S+@\S+\.\w+", text)
print(emails)
Raw strings
Always write patterns as r"...". Regex uses backslashes heavily, and so does Python — a raw
string stops them fighting:
print(len("\d"))
print(len(r"\d"))
Without the r, Python may interpret the escape before regex ever sees it. Make r"..." a
habit.
The building blocks
| Pattern | Matches |
| --- | --- |
| \d / \D | a digit / a non-digit |
| \w / \W | word character (letter, digit, _) / not |
| \s / \S | whitespace / non-whitespace |
| . | any character except newline |
| [abc] / [^abc] | one of these / none of these |
| [a-z] | a range |
Quantifiers say how many:
| Pattern | Means |
| --- | --- |
| * | zero or more |
| + | one or more |
| ? | zero or one |
| {3} / {2,4} | exactly 3 / between 2 and 4 |
Anchors say where:
| Pattern | Means |
| --- | --- |
| ^ | start of string |
| $ | end of string |
| \b | word boundary |
The main functions
import re
text = "The year 2026 follows 2025"
print(re.search(r"\d{4}", text).group())
print(re.findall(r"\d{4}", text))
print(re.sub(r"\d{4}", "YEAR", text))
print(re.split(r"\s+", "split these words"))
print(bool(re.match(r"The", text)))
search— first match anywhere, orNonefindall— every match, as a listsub— replacesplit— split on a patternmatch— only matches at the start
search returns None when nothing matches, so check before calling .group():
import re
result = re.search(r"\d+", "no digits here")
print(result)
print(result.group() if result else "no match")
Groups
Parentheses capture parts of a match:
import re
date_text = "Published 2026-03-15 by Ada"
m = re.search(r"(\d{4})-(\d{2})-(\d{2})", date_text)
print(m.group())
print(m.group(1))
print(m.groups())
Named groups are clearer than numbers:
import re
m = re.search(r"(?P<year>\d{4})-(?P<month>\d{2})", "2026-03-15")
print(m.group("year"))
print(m.group("month"))
print(m.groupdict())
Greedy versus lazy
* and + 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))
print(re.findall(r"<.+?>", html))
The greedy version swallows everything between the first < and the last >. This is one of
the most common regex surprises.
Compiling
Reusing a pattern? Compile it once:
import re
pattern = re.compile(r"\b\w+@\w+\.\w+\b")
print(pattern.findall("a@b.com and c@d.org"))
print(bool(pattern.search("no emails")))
When not to use regex
Regex is powerful and easily overused:
- Simple checks —
"@" in textortext.startswith("http")beats a pattern. - HTML — use a parser (lesson 8.6).
- Email validation — a truly correct pattern is monstrous. Check for
@and send a confirmation.
A regex you can't read six months later is a liability. If it needs a comment, consider a simpler approach.
Try it yourself
Checkpoint
- Always write patterns as raw strings,
r"...". \d \w \smatch classes;* + ? {n}set counts;^ $ \banchor position.searchfinds one,findallfinds all,subreplaces,matchanchors to the start.- Parentheses capture groups; named groups
(?P<name>...)read better. *and+are greedy — add?for lazy matching.- Don't use regex for HTML, or where a plain string method would do.
Questions about this lesson
Log in to ask a question.