Regular Expressions
Advanced · 20 min read · ▶ live playground · ✦ checkpoint
Your app gets one messy line of text: Mina paid $42 on 2026-07-15. You need the amount, the date, and maybe every email address nearby. Regular expressions, usually called regex, are a small pattern language for finding, checking, and changing text when plain string methods are too blunt.
Think in patterns, not words
A pattern is a description of what text should look like. Think of a regex as a wanted poster for text: it doesn't name one exact person, it describes the features Python should look for.
Use regex when the shape matters: "three digits", "a date like 2026-07-15", "text before an @ sign", or "every repeated space." Don't use regex when ordinary string tools say the job clearly. email.endswith(".com"), "@" in email, and line.split(",") are easier to read than regex for those small jobs.
Python's regex tools live in the re module.
import re
message = "Mina scored 42 points. Ravi scored 35 points."
start = re.match(r"Mina", message)
anywhere = re.search(r"Ravi scored \d+", message)
scores = re.findall(r"\d+", message)
hidden = re.sub(r"\d+", "[score hidden]", message)
print(start.group())
print(anywhere.group())
print(scores)
print(hidden)MinaRavi scored 35['42', '35']Mina scored [score hidden] points. Ravi scored [score hidden] points.
Successful match() and search() calls return a match object, a result that stores what Python found. .group() gives you the matched text.
re.match() checks only at the start of the string. re.search() scans until it finds the first match anywhere. re.findall() returns every match as a list of strings. re.sub() substitutes matching text with replacement text.
The r before each pattern makes a raw string, a string where backslashes stay as visible backslashes. Regex uses many backslashes, so raw strings keep patterns readable.
Build the wanted poster
A character class is a bracketed set of allowed characters for one position. [A-Z] means one uppercase letter. [aeiou] means one vowel. \d means one digit, and \w means one word character: a letter, digit, or underscore.
A quantifier says how many times the previous piece may repeat. + means one or more, * means zero or more, ? means optional, and {2,4} means at least 2 and at most 4. On the wanted poster, the character class says what feature to look for; the quantifier says how many times that feature appears.
An anchor pins the match to a location instead of describing a character. ^ means the start of the string, and $ means the end.
import re
codes = ["AX-42", "B-7", "MIND-1000", "rx-05", "NO-404"]
code_pattern = r"^[A-Z]{2,4}-\d{2,4}$"
for code in codes:
if re.match(code_pattern, code):
print(f"{code}: valid")
else:
print(f"{code}: check")AX-42: validB-7: checkMIND-1000: validrx-05: checkNO-404: valid
Read the pattern out loud: start of string, 2 to 4 uppercase letters, a dash, 2 to 4 digits, end of string. The anchors matter. With re.match(), ^ already keeps the match at the start, while $ blocks trailing junk like "AX-42 today".
Groups capture the useful parts
A group is a parenthesized part of a pattern that Python remembers separately. Capturing means saving that matched piece so you can use it after the search.
Groups are how regex moves from "I found a date" to "I found the year, month, and day."
import re
note = "Ada <ada@example.com> booked 2026-07-15. Backup: grace@school.org on 2026-08-02."
emails = re.findall(r"[\w.-]+@[\w.-]+\.[A-Za-z]{2,}", note)
dates = re.findall(r"(\d{4})-(\d{2})-(\d{2})", note)
ticket = re.search(r"#([A-Z])-(\d+)", "Order #A-2048 for Noor")
print(emails)
for year, month, day in dates:
print(f"{day}/{month}/{year}")
print(ticket.group(0))
print(ticket.group(1))
print(ticket.group(2))['ada@example.com', 'grace@school.org']15/07/202602/08/2026#A-2048A2048
When findall() sees groups, it returns the captured groups instead of only the whole match. That is why dates becomes a list of three-part records. With a single search result, .group(0) is the whole match, .group(1) is the first captured group, and .group(2) is the second.
Email regex is always a compromise. The short pattern above is useful for everyday text cleanup, but real email rules are stranger than most apps need. For account signup, the reliable check is usually "does this address receive a confirmation message?"
Try it - clean a contact sheet
This playground validates clean lines and hides email addresses before printing the public version. Change one date or email and watch which lines survive.
Compile patterns you reuse
Compiling a regex means asking Python to prepare a pattern object once, then reuse it. It keeps repeated searches tidy, and it can be faster when the same pattern runs many times.
A flag is an option that changes how a regex behaves. re.IGNORECASE ignores uppercase versus lowercase. re.MULTILINE makes ^ and $ work at the start and end of each line, not just the whole string.
import re
email_pattern = re.compile(r"^[\w.-]+@[\w.-]+\.[A-Za-z]{2,}$", re.IGNORECASE)
candidates = ["Mina@example.com", "bad address", "ravi@school", "NOOR@SCHOOL.ORG"]
for address in candidates:
if email_pattern.match(address):
print(f"{address}: send confirmation")
else:
print(f"{address}: ask again")
players = "Ada likes PYTHON.\nNoor likes python.\nRavi likes puzzles."
print(re.findall(r"^noor.*python\.", players, re.IGNORECASE | re.MULTILINE))Mina@example.com: send confirmationbad address: ask againravi@school: ask againNOOR@SCHOOL.ORG: send confirmation['Noor likes python.']
Compiled pattern objects have methods with the same names: email_pattern.match(...), email_pattern.search(...), email_pattern.findall(...), and email_pattern.sub(...). Use the module functions for one-off jobs. Compile when the pattern has a name in your problem.
Validate with humility
Validation means checking whether input follows the shape your program accepts. Regex is good at shape. It is not good at meaning.
For dates, r"^\d{4}-\d{2}-\d{2}$" checks the shape YYYY-MM-DD, but it still accepts 2026-99-99. In the next lesson, you'll use datetime to check whether a date is real. For HTML, JSON, or nested parentheses, use a parser, code that reads a structured format by its own rules. Regex is a scanner, not a full reader of every language.
For beginner-friendly validation, aim for this order:
- Use string methods when the rule is tiny.
- Use regex when the rule is a text shape.
- Use a parser or a domain tool when the text has real structure or meaning.
Checkpoint
Answer all three to mark this lesson complete
Regex gives you a compact scanner for messy human text, as long as you keep it pointed at text-shaped problems. Next you make dates and times mean something, not just look right.