Grouping controls precedence; most confusing regex bugs come from | applying to more than intended.
The | operator means or: cat|dog matches either word. It has the lowest precedence of all regex operators, so it splits the entire pattern (or the enclosing group) into alternatives. ^cat|dog$ is "cat at the start, or dog at the end", not "the whole string is cat or dog".
Parentheses group things:
- to limit alternation:
^(cat|dog)$,gr(a|e)y; - to apply a quantifier to a sequence:
(ha)+,(\d{1,3}\.){3}\d{1,3}; - to capture the matched text (next module).
If you only need grouping, not capturing, write a non-capturing group (?:...). It keeps capture numbering simple and avoids storing text you don't use. In Python, findall returns the groups instead of the whole match when the pattern contains capturing groups, which is another reason to prefer (?:...) for pure grouping.
Engines that backtrack try alternatives left to right and take the first that leads to an overall match, not the longest. So in|int against "integer" matches in. Put longer alternatives first, or add boundaries: \b(?:int|in)\b. POSIX tools like grep return the leftmost-longest match instead.
Example
import re
print(re.findall(r"cat|dog", "hotdog catalog")) # ['dog', 'cat']
print(bool(re.search(r"^cat|dog$", "catalog"))) # True: ^cat matched
print(bool(re.search(r"^(?:cat|dog)$", "catalog"))) # False
print(re.findall(r"gr(?:a|e)y", "gray grey groy")) # ['gray', 'grey']
print(re.findall(r"(?:ha)+", "hahaha!")) # ['hahaha']
print(re.search(r"in|int", "integer").group()) # in (first alternative wins)
print(re.search(r"int|in", "integer").group()) # int
print(re.findall(r"(ha)+", "hahaha")) # ['ha'] findall returns the group!Remember
- | has the lowest precedence: group alternatives with ( )
- Use (?:...) when you need grouping but not capturing
- Backtracking engines take the first alternative that works, not the longest
Watch out for
- Writing ^yes|no$ for 'yes or no only' instead of ^(?:yes|no)$.