Python RegEx
Practical Python Practical Python

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.

GoalBy the end you can write simple patterns with \d, \w, \s and quantifiers, use re.search, re.findall, re.fullmatch and re.sub, pull parts out of a match with groups, and recognise when plain string methods are the better tool.

Before this lesson: Python Strings, Python Lists

2Python RegEx: diagram

Python Regular Expressions (regex)"Call 555-0142"the textr"\d{3}-\d{4}"the patternre.search()or findall / sub"555-0142"match (None if no match)
You give the re module two things — the text and a pattern describing what you are looking for. It scans the text and hands back the match (or None), a list of all matches, or a new string with the matches replaced.

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

  • \d a digit, \w a word character (letter, digit or _), \s whitespace
  • . 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, \b the 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 — or None.
  • 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.
Python
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.

Python
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

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.

Interactive example
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.

  1. Line 1re is Python's built-in regular expression module.
  2. Line 3A message containing phone numbers, order IDs and a date, all mixed into ordinary text.
  3. Line 5-6\d{3}-\d{4} means three digits, a dash, four digits. findall() returns every match as a list of strings.
  4. 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 #.
  5. Line 11search() finds the first date. The three groups capture year, month and day separately.
  6. Line 12-14if m: guards against None (no match). m.groups() returns all groups as a tuple, which is unpacked and printed in day/month/year order.
  7. Line 16re.sub() replaces every phone number. \1 in the replacement puts back group 1 — the last four digits — so the numbers are masked, not deleted.
  8. Line 18-19fullmatch() checks the whole string: one capital letter and exactly four digits. a1042 fails on the lowercase letter and B20 is 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"))
Prints [] — no error, but nothing matches

✓ 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())
AttributeError: 'NoneType' object has no attribute '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))
Prints ['<b>bold</b> and <i>italic</i>'] — one huge match

✓ 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

Write 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.

Your code
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"))

2. Predict the matches

Predict the output Medium

Read each pattern carefully and type exactly what the program prints.

Read this code
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))

2 more exercises → 4 more examples

Finished this lesson?
Progress is saved in this browser — create a free account to keep it across devices.

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