Advanced Regex: Lookahead and Lookbehind Assertions
re.findall() is a Python method that extracts all non-overlapping substrings matching a regex pattern from a string.
The Search Trace
A regular expression can identify several occurrences inside one string, but the result from re.findall() depends on more than whether a pattern matches. You must also understand where the next search begins and whether the pattern contains capturing groups. Those two details explain many surprising results.
What do you think happens?
Suppose a pattern finds one complete occurrence in a line. Where does re.findall() continue searching?
Reveal answer
At the position immediately after the completed match
findall() scans from left to right, records a match, and resumes after that match ends. This produces non-overlapping results.
What findall() Returns
re.findall() extracts all non-overlapping substrings that match a regular expression pattern in a string. Its return structure depends entirely on the number of capturing groups in the pattern. Parentheses create capturing groups by defining a subpattern whose content is collected.
| Capturing groups | Return structure | What each result contains |
|---|---|---|
| None | List of strings | Each complete matching substring |
| Exactly one | List of strings | The content of that one group, not necessarily the complete match |
| Two or more | List of tuples | The captured groups in their pattern order |
The number of capturing groups controls the shape and content of the findall() result.
One Line, Several Matches
Extracting Addresses from an Email Log
An email log line contains several email addresses. Use findall() with an email-address pattern to collect every occurrence.
- 1Scan from the beginning
The regex engine starts at position 0 and moves left to right until it finds characters that can begin an email address.
- 2Record the first match
When the pattern matches an email address,
findall()records the complete matching substring. - 3Resume after the match
The next search begins immediately after the first matched address ends. An overlapping candidate inside that completed match is not returned.
- 4Repeat for later occurrences
The scan continues through the rest of the line and records each later non-overlapping email address that matches.
The result is a list containing the matching email-address strings in their left-to-right order.
For a single line with multiple occurrences, read the result in two ways: first, as a sequence of matches found from left to right; second, as a structure determined by the pattern's capturing groups.
Assertions and Match Boundaries
The article title places lookahead and lookbehind assertions in the topic area, but the supplied learning material does not define their syntax or specify their matching behavior. What the material does establish is the debugging principle that the exact pattern controls what findall() matches, how many characters it includes, and what values it returns. Therefore, when an assertion is part of a pattern, inspect it as one possible cause of an unexpected match boundary, while separately counting the pattern's capturing groups.
Diagnosing Unexpected Lists
Expected: complete matching substrings Observed: only smaller pieces of each match
The returned strings do not contain the whole expected match.
Check whether the pattern contains a capturing group. With one group, findall() returns the group's content rather than the complete match.
The result is interpreted according to the number of capturing groups.
Common mistake Assuming findall() always returns complete matching substrings.
What it looks likeA pattern includes one capturing group, so the returned strings contain only that group's content.
Why it failsWith exactly one capturing group,
findall()returns the content of that group rather than the complete match.Do this insteadCount the capturing groups before interpreting the result.
Common mistake Expecting overlapping matches.
What it looks likeA later candidate begins inside a match that has already been recorded.
Why it failsfindall()resumes scanning after the completed match, so results are non-overlapping.Do this insteadTrace the end of each match and mark where the next search begins.
Common mistake Ignoring quantifier behavior when match boundaries are unexpected.
What it looks likeA quantifier matches more or fewer characters than intended.
Why it failsQuantifiers can be greedy or lazy. By default, quantifiers are greedy and match as much as possible; adding ? after a quantifier makes it lazy.
Do this insteadInspect every *, +, or ? and decide whether its extent is intended.
Common mistake Debugging the returned list without testing the pattern itself.
What it looks likeThe output differs from expectation, but the pattern does not actually match the assumed text.
Why it failsThe result can be correct for a pattern that was misunderstood.
Do this insteadTest the regex pattern in isolation first, then inspect groups and quantifiers.
A Reliable Debugging Routine
- 1Write down the exact substrings you expect the pattern to match.
- 2Test the regex pattern in isolation and check whether it matches those substrings.
- 3Trace the first match from left to right and mark where it ends.
- 4Begin the next search after that ending position; do not assume overlapping candidates will be returned.
- 5Count the capturing groups created by parentheses.
- 6Interpret a list of strings or a list of tuples according to that group count.
- 7Inspect *, +, and ? quantifiers if the match includes too many or too few characters.
- 8If an assertion is present, isolate its contribution to the pattern and verify its assertion-specific behavior using the relevant regex reference.
A findall() call returns a list of tuples, but you expected a list of complete strings. What should you inspect first, and why?
Hints
- A list of tuples indicates that the pattern has multiple capturing groups.
- Check the parentheses in the pattern and the order of the groups.
- Then verify whether the complete match or only the captured groups are the values you actually need.
A pattern returns fewer results than expected from one line. Trace the first match, mark its ending position, and identify any candidate that begins before that position. Explain whether that candidate overlaps an existing match.
Hints
findall()scans from left to right.- The next search begins after the current match ends.
- An overlapping candidate will not be returned as a separate result.
Key Takeaways
- 1
re.findall()returns all non-overlapping matches found by scanning a string from left to right. - 2After recording a match,
findall()resumes searching immediately after that match ends. - 3No capturing groups or one capturing group produce a list of strings; two or more capturing groups produce a list of tuples.
- 4Unexpected output usually requires checking the pattern, the number of capturing groups, and the behavior of quantifiers.
- 5When lookahead or lookbehind assertions are present, analyze them as part of the pattern while separately checking match boundaries and return structure.
At a glance
findall()collects all non-overlapping matches in left-to-right order.- The next search begins after the previous match ends.
- Capturing-group count determines whether the result is a list of strings or a list of tuples.
- Debugging should proceed through pattern matching, group counting, match boundaries, and quantifier behavior.
- The supplied material does not define the detailed syntax or consumption rules of lookahead and lookbehind assertions, so those details require a dedicated regex reference.