Python Regular Expressions (regex)
1Python RegEx: overview
A regular expression (regex) is a short pattern that describes the shape of some text — such as "three digits, a dash, then four digits" — which Python's re module uses to find, extract, check and replace text.
Before this lesson: Python Strings, Python Lists
2Python RegEx: diagram
3Python RegEx: explained step by step
Some text jobs are awkward with split() and in: find every price in a paragraph, check that a code has the right format, pull the date out of a log line. A regular expression describes the shape of the text you want, and the re module finds it for you.
Always use raw strings
Patterns are full of backslashes: \d, \s, \b. In a normal Python string, a backslash starts an escape sequence — "\b" is a backspace character, not a regex word boundary. Put an r in front of the pattern to make it a raw string, so every backslash reaches the regex engine untouched: r"\d+".
The building blocks
\da digit,\wa word character (letter, digit or_),\swhitespace.any character except a newline;\.a real dot[aeiou]one character from the set;[^0-9]any character except a digit+one or more,*zero or more,?optional,{3}exactly three,{2,4}two to four^start of the text,$end of the text,\bthe edge of a word
A quantifier applies to the item just before it, so \d+ means "one or more digits".
Searching and extracting
re.search(pattern, text)finds the first match anywhere and returns a match object — orNone.re.findall(pattern, text)returns a list of every match.re.fullmatch(pattern, text)only succeeds if the whole text fits the pattern, which makes it ideal for validation.
import re
text = "Order 66 shipped on 2024-05-17, order 71 is pending."
print(re.findall(r"\d+", text))
m = re.search(r"(\d{4})-(\d{2})-(\d{2})", text)
if m:
print("Found:", m.group())
print("Year:", m.group(1), "Month:", m.group(2))Groups
Parentheses create groups that capture parts of a match. m.group() is the whole match and m.group(1) is the first group. When a pattern contains groups, findall() returns the groups instead of the whole match — a tuple for each match if there are several groups.
Replacing with re.sub()
re.sub(pattern, replacement, text) replaces every match. In the replacement, \1 puts back whatever group 1 captured.
import re
messy = "Too many spaces here"
print(re.sub(r"\s+", " ", messy))
print(re.sub(r"(\w+)@example\.com", r"\1 (at) example", "Write to asha@example.com"))When not to use regex
Regex is powerful but hard to read later. If a string method does the job — in, startswith(), endswith(), split(), replace(), isdigit() — use it. Don't use regex to pick apart structured formats like JSON, CSV or HTML either; the json and csv modules and HTML parsers handle their edge cases correctly. And keep validation patterns simple: a strict email regex is famously long, so check for the basic shape and let a confirmation email do the rest.
Quick reference: Python RegEx
- Python Regex Cheat Sheet (re Module Quick Reference) — Python regular expressions live in the `re` module: write the pattern as a raw string, then use `re.search()`…
- How to Remove Whitespace from a String in Python — Use `text.strip()` to remove whitespace from both ends, or `"".join(text.split())` to remove it everywhere, i…
- How to Split a String into Words in Python — Call `text.split()` with no arguments: it splits on any whitespace and returns a list of words, such as `"to …
4Python RegEx: syntax
import re
re.search(r"pattern", text) # first match -> Match object or None
re.findall(r"pattern", text) # list of every match
re.fullmatch(r"pattern", text) # the WHOLE text must match
re.sub(r"pattern", "new", text) # replace every match
m.group() m.group(1) # whole match, first ( ) group
\d digit \w word char \s whitespace . any char \b word edge
+ one or more * zero or more ? optional {n} exactly n [abc] one of
5Python RegEx: run the example
Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.
import re
message = "Call 555-0142 or 555-0199. Orders #A1042 and #B2077 shipped 2024-05-17."
phones = re.findall(r"\d{3}-\d{4}", message)
print("Phones:", phones)
order_ids = re.findall(r"#([A-Z]\d+)", message)
print("Order IDs:", order_ids)
m = re.search(r"(\d{4})-(\d{2})-(\d{2})", message)
if m:
year, month, day = m.groups()
print(f"Shipped: {day}/{month}/{year}")
print(re.sub(r"\d{3}-(\d{4})", r"***-\1", message))
for code in ["A1042", "a1042", "B20"]:
print(code, bool(re.fullmatch(r"[A-Z]\d{4}", code)))Expected outputPhones: ['555-0142', '555-0199']
Order IDs: ['A1042', 'B2077']
Shipped: 17/05/2024
Call ***-0142 or ***-0199. Orders #A1042 and #B2077 shipped 2024-05-17.
A1042 True
a1042 False
B20 False
7Python RegEx: how the example works
Hover or tap a step to highlight its lines in the example above.
- Line 1
reis Python's built-in regular expression module. - Line 3A message containing phone numbers, order IDs and a date, all mixed into ordinary text.
- Line 5-6
\d{3}-\d{4}means three digits, a dash, four digits.findall()returns every match as a list of strings. - Line 8-9The
#must appear literally; the group([A-Z]\d+)captures one capital letter followed by digits. Because the pattern has a group,findall()returns just the group — without the#. - Line 11
search()finds the first date. The three groups capture year, month and day separately. - Line 12-14
if m:guards againstNone(no match).m.groups()returns all groups as a tuple, which is unpacked and printed in day/month/year order. - Line 16
re.sub()replaces every phone number.\1in the replacement puts back group 1 — the last four digits — so the numbers are masked, not deleted. - Line 18-19
fullmatch()checks the whole string: one capital letter and exactly four digits.a1042fails on the lowercase letter andB20is too short.
8Python RegEx: common mistakes
Forgetting the r in front of the pattern
✗ Wrong
import re
print(re.findall("\bcat\b", "cat catalog cat"))✓ Right
import re
print(re.findall(r"\bcat\b", "cat catalog cat"))In a normal string "\b" is a backspace character, so the regex engine never sees a word boundary. Write every pattern as a raw string.
Using the result of search() without checking for None
✗ Wrong
import re
m = re.search(r"\d+", "no numbers here")
print(m.group())✓ Right
import re
m = re.search(r"\d+", "no numbers here")
if m:
print(m.group())
else:
print("No number found")re.search() returns None when nothing matches. Check the result with if m: before calling .group().
A greedy .* that swallows too much
✗ Wrong
import re
html = "<b>bold</b> and <i>italic</i>"
print(re.findall(r"<.*>", html))✓ Right
import re
html = "<b>bold</b> and <i>italic</i>"
print(re.findall(r"<[^>]+>", html))* is greedy: it grabs as much as possible, from the first < to the very last >. Use a character set that cannot cross a >, or the non-greedy .*?.
9Python RegEx: exercises
Write your answer, then press Check. The checks run in your browser.
1. Extract every number
Write code Easy ✓ CompletedWrite extract_numbers(text) that returns a list of ints, one for every whole number in text, in order. Return an empty list if there are none.
import re
def extract_numbers(text):
# find every run of digits and return them as ints
pass
print(extract_numbers("I bought 3 apples for 120 rupees"))
re.findall(r"\d+", text) gives a list of strings. Convert each one with int(), for example in a list comprehension.import re
def extract_numbers(text):
return [int(n) for n in re.findall(r"\d+", text)]
print(extract_numbers("I bought 3 apples for 120 rupees"))
2. Predict the matches
Predict the output Medium ✓ CompletedRead each pattern carefully and type exactly what the program prints.
import re
text = "cat cart scat car"
print(re.findall(r"ca.", text))
print(re.findall(r"\bca\w*", text))
print(re.sub(r"a", "o", "banana", count=2))
. matches exactly one character. \b needs a word edge before ca, so the ca inside scat doesn't count. count=2 limits how many replacements happen.['cat', 'car', 'cat', 'car']
['cat', 'cart', 'car']
bonona
2 more exercises → 4 more examples
Questions learners ask
What is the difference between re.match, re.search and re.fullmatch?
re.search looks for the pattern anywhere in the text. re.match only matches at the very start of the text. re.fullmatch requires the entire text to match, which is what you want when validating input such as a postcode or username.
Why should I write regex patterns as raw strings?
Regex uses many backslashes, and Python's normal strings treat backslashes as escape sequences: "\b" becomes a backspace and "\d" triggers a warning. A raw string such as r"\b\d+\b" passes every backslash to the regex engine unchanged.
Should I use regex to validate email addresses?
Only for a rough check, such as r"[^@\s]+@[^@\s]+.[^@\s]+". A fully correct email pattern is extremely long and still can't tell whether the address exists. The reliable test is sending a confirmation email.
Further reading
10Next lesson
Up nextPython requests: Call Web APIsThe requests library lets your Python code send HTTP requests to web servers and APIs and read their responses — most o…Related:
Python StringsWorking with JSON in PythonPython CSV Files
← Python CSV Files · All Practical Python lessons · Practise in the IDE