Chapters
Python114 chapters

Modules and the standard libraryChapter 75 of 114

RegEx

Find and replace text by pattern rather than by exact match.

The re module

A regular expression describes a shape of text. re matches those shapes:

Python
import re

text = "call 0176 1234567 or 0151 7654321"
print(re.findall(r"\d+", text))

Output

['0176', '1234567', '0151', '7654321']

Always write patterns as raw strings. Without the r, Python processes the backslashes before re ever sees them.

The pieces worth knowing

PatternMatches
\da digit
\wa letter, digit or underscore
\swhitespace
.any character except newline
[abc]one of a, b or c
[^abc]anything except those
+one or more
*zero or more
?zero or one
{2,4}between two and four
^ / $start / end of the string
\ba word boundary
Python
import re

print(re.findall(r"\b\w{5}\b", "the quick brown fox jumps"))
print(re.findall(r"[aeiou]", "programming"))
print(re.findall(r"^\w+", "first word only"))

Output

['quick', 'brown', 'jumps']
['o', 'a', 'i']
['first']

The main functions

Python
import re

text = "Ada was born in 1815"

print(re.search(r"\d{4}", text).group())
print(re.findall(r"\w+", text))
print(re.sub(r"\d{4}", "YYYY", text))
print(re.split(r"\s+", text))

Output

1815
['Ada', 'was', 'born', 'in', '1815']
Ada was born in YYYY
['Ada', 'was', 'born', 'in', '1815']
  • search finds the first match anywhere, or None
  • match only matches at the start
  • findall gives every match as a list
  • sub replaces
  • split breaks on the pattern

search returns None when there is nothing

Python
import re

found = re.search(r"\d+", "no digits here")
print(found)

if found:
    print(found.group())
else:
    print("nothing matched")

Output

None
nothing matched

Calling .group() on that None raises AttributeError, which is the most common regex mistake. Check first.

Capture groups

Brackets capture part of the match:

Python
import re

match = re.search(r"(\d{2})/(\d{2})/(\d{4})", "dated 14/03/2026 exactly")
print(match.group(0))
print(match.group(1))
print(match.groups())

Output

14/03/2026
14
('14', '03', '2026')

Group 0 is the whole match; the rest are the brackets, left to right.

Named groups read far better once there are more than two:

Python
import re

pattern = r"(?P<day>\d{2})/(?P<month>\d{2})/(?P<year>\d{4})"
match = re.search(pattern, "14/03/2026")
print(match.group("year"))
print(match.groupdict())

Output

2026
{'day': '14', 'month': '03', 'year': '2026'}

With groups, findall returns the groups rather than the whole match:

Python
import re
print(re.findall(r"(\w+)@(\w+)\.com", "a@x.com b@y.com"))

Output

[('a', 'x'), ('b', 'y')]

Greedy by default

+ and * take as much as they can. Add ? to take as little:

Python
import re

html = "<b>bold</b> and <i>italic</i>"
print(re.findall(r"<.+>", html))
print(re.findall(r"<.+?>", html))

Output

['<b>bold</b> and <i>italic</i>']
['<b>', '</b>', '<i>', '</i>']

The greedy version ran from the first < to the last >. This surprises everyone once.

Flags

Python
import re

print(re.findall(r"ada", "Ada and ADA", re.IGNORECASE))
print(re.sub(r"\s+", " ", "too    much\n\nspace"))

Output

['Ada', 'ADA']
too much space

Compile a pattern you reuse

Python
import re

word = re.compile(r"\b[A-Z]\w+")
print(word.findall("Ada met Grace in London"))
print(word.sub("X", "Ada met Grace"))

Output

['Ada', 'Grace', 'London']
X met X

When not to use it

A regex is powerful and hard to read. If a string method does the job, use it:

Python
name = "report.pdf"
print(name.endswith(".pdf"))
print(name.split("."))

Output

True
['report', 'pdf']

Test yourself

3 questions

Why should regex patterns be raw strings?

Show the answer

Otherwise Python processes the backslashes before re sees them — Without the r you end up escaping every backslash twice.

What does re.search return when nothing matches?

Show the answer

None — Calling .group() on that None is the most common regex mistake. Check the result first.

Why does <.+> match the whole string rather than one tag?

Show the answer

+ is greedy and takes as much as it can — Add a ? to make it lazy: <.+?> matches each tag separately.

Next chapter

itertools

Building blocks for looping, that never build the whole sequence.