Python Regular Expressions

Find and change patterns in text with the re module: character classes, quantifiers, groups, findall, sub and validating input.

Lesson 44 of 107Advanced18 min read

String methods like find(), replace() and split() are perfect when you know exactly what text you're looking for. But what if you want every phone number in a document, every price in a web page, or to check that a postcode looks valid? You don't know the exact text, only its shape: "three digits, a dash, four digits".

A regular expression, or regex, describes that shape. It's a small pattern language supported by almost every programming language, editor and database. In Python, regular expressions live in the built-in re module.

Regex has a reputation for looking cryptic, and long patterns can be. But a handful of symbols covers most everyday needs. In this lesson you'll learn those symbols, then use them to search, extract, replace and validate text, with patterns you can read and explain.

Your first patterns

Python
import re
text = "Order 4521 shipped on 12 March"
match = re.search(r"\d+", text)
print(match.group())    # 4521
  • Write patterns as raw strings, r"...", so backslashes reach re unchanged.
  • re.search() finds the first match anywhere in the text, or returns None.
  • match.group() gives the text that matched; match.start() gives its position.
  • \d means "any digit" and + means "one or more", so \d+ matches a whole number.

The building blocks

PatternMatches
\d / \DA digit / anything except a digit
\wA letter, digit or underscore
\sWhitespace: space, tab, newline
.Any character except a newline
[aeiou] / [A-Z]One character from a set / range
[^0-9]Any character not in the set
^ / $Start / end of the text
\bA word boundary

Quantifiers say how many: * zero or more, + one or more, ? optional, {3} exactly three, {2,4} two to four. To match a special character literally, escape it: \. matches a real full stop.

search, match and fullmatch compared

The three matching functions differ only in where the pattern must match, and confusing them is a common source of bugs:

Python
import re
code = "AB-1234"
print(bool(re.search(r"\d{4}", code)))      # True: digits anywhere
print(bool(re.match(r"\d{4}", code)))       # False: must start at position 0
print(bool(re.fullmatch(r"[A-Z]{2}-\d{4}", code)))  # True: whole string
  • search scans the whole string for the first place the pattern fits.
  • match only tries at the very beginning.
  • fullmatch requires the pattern to cover the entire string, which makes it the right tool for validating input.

Greedy and lazy quantifiers

By default, * and + are greedy: they match as much text as possible. That surprises people extracting text between markers:

Python
html = "<b>bold</b> and <b>more</b>"
print(re.findall(r"<b>.*</b>", html))   # ['<b>bold</b> and <b>more</b>']
print(re.findall(r"<b>.*?</b>", html))  # ['<b>bold</b>', '<b>more</b>']

Adding ? after a quantifier makes it lazy, matching as little as possible. For real HTML, use a parser such as BeautifulSoup rather than regex, but the greedy-versus-lazy idea applies to any text with repeated delimiters.

If you use the same pattern many times, re.compile() creates a pattern object with the same methods: postcode = re.compile(r"..."), then postcode.fullmatch(text). Python caches recently used patterns anyway, so compiling is mainly about readability and naming.

findall, sub and split

  • re.findall(pattern, text) returns a list of every match.
  • re.sub(pattern, replacement, text) replaces every match, which is great for cleaning data.
  • re.split(pattern, text) splits wherever the pattern matches, for example on commas or semicolons: re.split(r"[,;]\s*", text).
  • re.fullmatch(pattern, text) succeeds only if the whole text matches, which is what you want for validation.

Add flags=re.IGNORECASE to any of them to ignore capital letters.

Groups

Round brackets create a group, which captures part of the match separately:

Python
m = re.search(r"(\d{2})/(\d{2})/(\d{4})", "Due 05/03/2025")
day, month, year = m.groups()

Name groups with (?P<name>...) and read them with m["name"], which makes long patterns easier to understand. In re.sub, refer to groups in the replacement with \1, \2 and so on, for example to reorder dates.

Python Regular Expressions Syntax

re functions

import re
re.search(r"pattern", text)        # first match or None
re.fullmatch(r"pattern", text)     # whole text must match
re.findall(r"pattern", text)       # list of matches
re.sub(r"pattern", "new", text)    # replace
re.split(r"pattern", text)         # split
re.compile(r"pattern")             # reuse a pattern
m.group()  m.groups()  m["name"]

Python Regular Expressions Summary

  • The re module provides regular expressions; write patterns as raw strings r"...".
  • \d, \w, \s, . and [...] match kinds of characters; +, *, ? and {n} say how many.
  • search finds the first match, findall all of them, fullmatch checks the whole text.
  • sub replaces matches and split splits on a pattern.
  • Brackets create groups, which can be named and reused in replacements.

Python Regular Expressions Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Extracting prices

Python
import re

page = "Tea £2.50, Cake £3.95, Water £1, Gift card £20.00"
prices = re.findall(r"£(\d+(?:\.\d{2})?)", page)
print(prices)
print("Total:", sum(float(p) for p in prices))
Output
['2.50', '3.95', '1', '20.00']
Total: 27.45
Line-by-line explanation
  1. Line 4£ then one or more digits, then optionally a dot and two digits. (?:...) groups without capturing, so findall returns only the outer group: the number.
  2. Line 6The matches are strings, so convert them to floats before adding.
Example 2

Validating a username

Python
import re

pattern = r"[a-z][a-z0-9_]{2,14}"
for name in ["sam_lee", "7ninja", "ab", "codevra_fan_2025"]:
    if re.fullmatch(pattern, name):
        print(f"{name}: ok")
    else:
        print(f"{name}: invalid")
Output
sam_lee: ok
7ninja: invalid
ab: invalid
codevra_fan_2025: invalid
Line-by-line explanation
  1. Line 3A lowercase letter, then 2 to 14 lowercase letters, digits or underscores: 3 to 15 characters in total.
  2. Line 5fullmatch checks the entire name, not just part of it. The last name is 16 characters, one too many.
Example 3

Cleaning and reformatting

Python
import re

messy = "Call  07700 900123   or 07700-900456 today"
clean = re.sub(r"\s+", " ", messy)
print(clean)

dates = "Start 2025-03-05, end 2025-04-30"
print(re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", dates))
Output
Call 07700 900123 or 07700-900456 today
Start 05/03/2025, end 30/04/2025
Line-by-line explanation
  1. Line 4Any run of whitespace becomes a single space.
  2. Line 8Three groups capture year, month and day, and the replacement puts them in a new order.

Common Python Regular Expressions Mistakes

Forgetting the r prefix

Don't
re.search("\bcat\b", text)    # \b becomes a backspace character
Do
re.search(r"\bcat\b", text)

Using search to validate

Don't
re.search(r"\d{5}", "abc123456xyz")      # matches, but isn't a 5-digit code
Do
re.fullmatch(r"\d{5}", "abc123456xyz")   # None

Not checking for None

Don't
m = re.search(r"\d+", "no numbers")
print(m.group())     # AttributeError: 'NoneType'
Do
m = re.search(r"\d+", "no numbers")
if m:
    print(m.group())

Python Regular Expressions in Real Projects

Regex in real work

Developers use regex to pull values out of log files, validate form fields, rename hundreds of files, clean up scraped data and search code in their editor. Data analysts use the same patterns in pandas and SQL. Learning a small set of symbols pays off for years, because the syntax is almost identical across languages.

Python Regular Expressions FAQ

What is the difference between re.search() and re.match()?

re.search() looks for the pattern anywhere in the string. re.match() only checks whether the string starts with it. For validating a whole value, use re.fullmatch(), which requires the pattern to match from the first character to the last.

Why should regex patterns be raw strings?

In a normal string, Python treats backslashes as escape characters first, so "\b" becomes a backspace character before re ever sees it. A raw string, r"\b", passes the backslash through unchanged. Using r"..." for every pattern avoids this whole class of bugs.

Can I validate an email address with a regex?

You can check that it has a plausible shape, such as r"[^@\s]+@[^@\s]+\.[^@\s]+", which catches obvious typos. A pattern that accepts every valid address and rejects every invalid one is extremely complicated. The only real proof an address works is sending a confirmation email to it.

How do I make a regex ignore upper and lower case?

Pass flags=re.IGNORECASE (or re.I) to the function, for example re.findall(r"python", text, flags=re.I). You can also put (?i) at the very start of the pattern itself.

How do I match a literal dot, bracket or question mark?

Escape it with a backslash: \., \(, \?. Inside square brackets most special characters lose their meaning, so [.?] matches a dot or a question mark. re.escape(text) escapes every special character in a string for you, which is useful when part of the pattern comes from user input.

Python Regular Expressions Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the pattern so it finds all whole numbers in the text.

re.findall(r"\d", "3 cats and 12 dogs")
Predict the outputeasy

What does this print?

import re
print(re.findall(r"[aeiou]", "regex"))
Predict the outputmedium

What does this print?

import re
print(re.sub(r"\d", "#", "PIN 4821"))
Multiple choicemedium

Which pattern matches exactly a UK-style time like 09:45?

Multiple choicehard

Why use re.fullmatch for validation instead of re.search?

Python Regular Expressions Quiz

5 questions to check what you learned.

  1. Which module provides regular expressions in Python?

    • regex
    • re
    • string
    • pattern
  2. What does ? mean after a character in a pattern?

    • One or more
    • Zero or more
    • Optional: zero or one
    • Exactly one
  3. What does re.search return when nothing matches?

    • An empty string
    • False
    • None
    • An error
  4. What does \s match?

    • A whitespace character
    • The letter s
    • Any symbol
    • The start of the text
  5. In re.sub(r"(\w+) (\w+)", r"\2 \1", "Ada Lovelace"), what's the result?

    • Ada Lovelace
    • \2 \1
    • AdaLovelace
    • Lovelace Ada