Choosing the Right Match API
Choosing the Right Match API
Each function answers a different question:
| Call | Question |
|---|---|
re.search(r"\d+", s) |
does any substring match? |
re.match(r"\d+", s) |
does it match at the start? |
re.fullmatch(r"\d+", s) |
does the whole string match? |
re.findall(r"(\w+)@(\w+)", s) |
all matches (groups only, when groups exist) |
re.split(r"[,;]", s) |
split keeping delimiters if they are captured |
re.sub(r"\s+", " ", s) |
normalize replacements |
Write patterns as raw strings — r"\d{3}-\d{4}" — so backslashes reach the engine instead of becoming Python escapes. re.match is not "starts with anchor": it never scans forward, so re.match(r"end", "send") fails while re.search succeeds.
Groups, Anchors, and Quantifiers
Groups, Anchors, and Quantifiers
import re
m = re.search(r"(?P<user>\w+)@(?P<host>[\w.]+)", "mail alice@example.com here")
m.group("user") # 'alice' — named groups read better in code
re.sub(r"\b(\w)(\w*)", r"\2\1-AY", "python") # groups in replacement text
Quantifier behavior matters:
x+/x*are greedy (match as much as possible);x+?/x*?are lazy^\w+$anchors start/end;\bmarks a word boundary — essential for whole-word replaces(?:...)groups without capturing (cheaper, cleaner when you only need grouping)(?i),(?m), inline flags orflags=re.IGNORECASE | re.MULTILINE:.normally skips newlines unlessre.DOTALL
Compilation, Performance, and Pitfalls
Compilation, Performance, and Pitfalls
Module-level calls cache up to 512 compiled patterns, but hot loops should be explicit:
VALID = re.compile(r"^[A-Z]{2}-\d{4}$") # compile once, reuse everywhere
if VALID.fullmatch(code): ...
Catastrophic backtracking is the classic interview trap: (a+)+$ against "aaaaaaaaaaaaaaaaX" explores exponentially many partitions. Fix it by removing nested quantifiers, anchoring precisely, or — on Python 3.11+ — using possessive quantifiers a++ and atomic groups (?>...) that forbid backtracking.
Other gotchas: \d matches Unicode digits (narrow with [0-9] if you need ASCII), splitting on zero-width matches yields empty strings, and re.escape is mandatory when interpolating user text into a pattern.
Quiz
1. re.match(r"bc", "abcd") returns what?
2. Why can (a+)+$ blow up on long non-matching input?
3. Which pattern is lazy?
Flashcards
Question
match vs search vs fullmatch
Click to reveal answer
Answer
match = anchored at index 0; search = anywhere; fullmatch = entire string.
Question
Greedy vs lazy
Click to reveal answer
Answer
+/* are greedy (max match); +?/*? are lazy (min match until the rest can match).
Question
re.compile use
Click to reveal answer
Answer
Compile patterns used repeatedly: pattern.match(s) reuses the compiled object.
Question
Catastrophic backtracking
Click to reveal answer
Answer
Nested quantifiers like (a+)+$ — fix by flattening, anchoring, or possessive/atomic constructs (3.11+).
Revision Notes
Key Takeaways
- 1. Pick the API by the question: fullmatch for validation, search for discovery, findall/sub for transforms
- 2. Raw strings, named groups, and explicit flags keep patterns readable
- 3. Compile once and design patterns to avoid exponential backtracking
Interview Tips
- • When asked to validate input, reach for fullmatch and say why partial matches are dangerous
- • Name the failure mode of nested quantifiers and sketch the flattened rewrite
Cheat Sheet
search/anywhere, match/index0, fullmatch/whole; r"" raw; re.compile reuse; greedy vs lazy (a* vs a*?); anchors ^ $ \b; avoid (a+)+$; possessive ++ in 3.11+.