Guide Python Intermediate

9.10 Regular Expressions with Strings

The re module -- match() vs search(), findall()/finditer(), sub()/split(), pattern compilation, capture groups and named groups, and lookahead/lookbehind assertions.

3 min read

What Is Regex?

A regular expression is a mini-language for describing text patterns — used for validation, extraction, and bulk find/replace. Python’s built-in re module implements it, and it’s the production-grade alternative to the naive character-by-character search shown in 9.9 String Algorithms for anything beyond simple literal matching.

flowchart LR G["(?P<year>\\d{4})-(?P<month>\\d{2})-(?P<day>\\d{2})"] G --> Y["year group\n\\d{4}"] G --> M["month group\n\\d{2}"] G --> D["day group\n\\d{2}"]

A named-group pattern for 2026-07-13: match.group("year")"2026".

re.match() vs re.search()

match() only succeeds if the pattern matches starting at position 0; search() scans the whole string for the first match anywhere. Use match() to validate that an entire string starts a certain way, search() to find something buried inside larger text.

>>> import re
>>> re.match(r"\d+", "123abc")        # anchored at the START of the string
<re.Match object; span=(0, 3), match='123'>
>>> re.search(r"\d+", "abc123def")    # finds the FIRST match anywhere
<re.Match object; span=(3, 6), match='123'>

re.findall() / re.finditer()

findall() collects every match into a list of strings; finditer() gives the same matches lazily as Match objects, useful when each match’s position is also needed.

>>> re.findall(r"\d+", "a1 b22 c333")
['1', '22', '333']

re.sub() / re.split()

sub() replaces every match with a given string — the regex equivalent of str.replace() but pattern-based. split() breaks a string apart wherever the pattern matches, useful when the separator itself is irregular (variable amounts of whitespace, for example).

>>> re.sub(r"\s+", " ", "too    many   spaces")
'too many spaces'
>>> re.split(r",\s*", "a, b,c,  d")
['a', 'b', 'c', 'd']

Pattern Compilation

Compiling a pattern once and reusing it for repeated matching (e.g. inside a loop over thousands of log lines) avoids re-parsing the pattern every call.

>>> pattern = re.compile(r"\d+")
>>> pattern.findall("a1 b22")
['1', '22']

Groups and Named Groups

Parentheses in a pattern capture matched sub-text for later use; groups() returns all captures as a tuple in order. (?P<name>...) attaches a label so a group can be retrieved by name instead of position — far more readable with many groups, and less fragile to reorder.

>>> m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})", "2026-07-13")
>>> m.group("year"), m.group("month"), m.group("day")
('2026', '07', '13')

Lookahead and Lookbehind

A lookahead (?=...) matches a position only if followed by a given pattern, without including that pattern in the match — useful for a value only when it has a specific unit or suffix nearby. A lookbehind (?<=...) is the mirror image.

>>> re.findall(r"\d+(?=px)", "10px 20em 30px")     # digits followed by 'px'
['10', '30']
>>> re.findall(r"(?<=\$)\d+", "$50 and 30")        # digits preceded by '$'
['50']

Quick Interview Answer

match() anchors at position 0; search() finds a match anywhere. findall() returns matched strings; finditer() returns lazy Match objects with position info. sub() and split() are the pattern-based equivalents of str.replace() and str.split(). Capture groups pull structured sub-fields out of a match — named groups ((?P<name>...)) make multi-group patterns readable instead of positional and fragile. Lookahead/lookbehind assert that a pattern exists nearby without consuming it into the match itself. Compiling a pattern once with re.compile() avoids re-parsing it on every call inside a hot loop.”

Common Mistakes

  • Using match() when search() is actually needed — match() silently returns None for anything not anchored at position 0, which is easy to misdiagnose as “the pattern is wrong.”
  • Forgetting parentheses in a pattern create a capturing group even when capture isn’t the intent — use (?:...) for a non-capturing group if grouping is only needed for precedence.
  • Re-compiling the same pattern inside a loop instead of compiling it once outside — wasteful when processing many lines, as in log parsing (see 9.12 Strings in DevOps and AWS).

Add More Questions to This Guide

Know a question that should be here? Share it and help the community!

Open Google Form