Groups and Captures
Regular Expressions in Practice
Chapter 2 ยท Groups and Captures
So far a match has been one lump of text. Real work usually needs the pieces: the year, month and day of a date, the user and host of an address. Parentheses turn part of a pattern into a group, and the engine remembers what each group matched. This chapter covers numbered groups, named groups, groups that do not capture, and how findall changes its answer when groups appear.
Numbered Groups
Each pair of parentheses is a group, numbered from 1 by the position of its opening bracket, reading left to right. Group 0 is always the whole match.
.groups() returns every group except group 0 as a tuple, which makes unpacking into variables a one-liner. .span(n) gives the position of one group inside the original string. Captured values are always strings: convert with int() when you need a number.
Nested groups
When groups sit inside one another, count opening brackets from the left. The outer group is numbered before the ones inside it.
Groups That Did Not Take Part
A group followed by ? may match nothing. It still exists, but its value is None, not an empty string. Check for that before using it.
Named Groups
Numbers are fragile: add a group at the front and every number after it shifts. A named group, written (?P<name>...), lets you ask for a part by what it means. It still has a number too.
.groupdict() returns a dictionary of name to text, which is ideal for building records. The shorthand m["month"] works the same as m.group("month"). Together with finditer you can turn a line of text into data:
Non-Capturing Groups
Sometimes you need parentheses only to group: to apply a quantifier to several characters, or to limit an alternation. If you do not want the text remembered, write (?:...). It takes no number and does not appear in .groups().
| Syntax | Groups? | Captures? | Use it for |
|---|---|---|---|
( ... ) | yes | yes, numbered | pieces you want back |
(?P<name> ... ) | yes | yes, named and numbered | pieces with a meaning |
(?: ... ) | yes | no | repeating or choosing, nothing to keep |
How findall Reacts to Groups
This is the rule that surprises people. What findall returns depends on how many capturing groups the pattern has:
| Capturing groups | findall returns |
|---|---|
| none | a list of the whole matches |
| exactly one | a list of that group's text |
| two or more | a list of tuples, one item per group |
The last two lines are the classic accident. Adding a capturing group just to repeat it changes the result to the last repetition of that group ('0.' and '1.'), not the address. Use (?:...) when the parentheses are only there for repeating. A repeated capturing group always keeps only its final repetition:
Reusing a Group Inside the Pattern
A backreference such as \1 means "the same text group 1 matched". It lets a pattern find repetition, like a word typed twice in a row.
Challenges
Given s = "Smith, John; Doe, Jane", use findall with two capturing groups to get the surname and first name of each person as tuples, then build the list ['John Smith', 'Jane Doe'] from them.
Write parse_time(t) using named groups h, m and an optional s for strings like 9:05 and 14:30:59. Return a dictionary of integers (seconds default to 0) or None if the string does not fit. Test it on "9:05", "14:30:59", "9:5" and "1:2:3". Hint: the optional seconds need a non-capturing group around the colon.
For urls = "http://a.com/x https://b.org/y/z ftp://c.net", predict and then run four things: findall with a capturing protocol group and a capturing host group; the same with the protocol group made non-capturing; the same with both groups named; and a finditer loop printing each groupdict(). Explain why the shape of the findall result changes.
Quick Reference
- Group:
( ... )numbered from 1 by opening bracket; group 0 is the whole match - Read back:
m.group(n),m.groups(),m.span(n) - Named:
(?P<name> ... ), thenm["name"]orm.groupdict() - Non-capturing:
(?: ... )groups without remembering - Unused group: value is
None - findall: strings for 0 or 1 groups, tuples for 2 or more
- Backreference:
\1repeats what group 1 matched