Substituting and Splitting
Regular Expressions in Practice
Chapter 4 ยท Substituting and Splitting
So far patterns have only found text. This chapter uses them to change it. re.sub replaces every match with something else, and re.split cuts a string wherever a pattern matches. Both are far more flexible than the plain string methods str.replace and str.split.
re.sub
The call is re.sub(pattern, replacement, string, count=0, flags=0). It returns a new string; the original is never changed. A count of 0 means "replace all".
re.subn works the same way but returns a tuple of the new string and the number of replacements, which is useful when you need to know whether anything changed.
Backreferences in the replacement
The replacement string can reuse what the pattern captured. Write \1, \2 for numbered groups, or \g<name> for named ones. This is how you reorder text.
The last line shows why \g<1> exists: \10 would be read as group ten, not group one followed by a zero. Use the \g<...> form whenever a digit follows the reference.
Backreferences inside the pattern
A group can also be referred to within the pattern itself, to demand a repeat of whatever it matched.
Here (\w)\1 matches a doubled letter. In the pattern, \1 means "the same character group 1 just matched"; in the replacement, it means "insert group 1".
A function as the replacement
When plain reordering is not enough, pass a function. re.sub calls it once per match with the match object and substitutes whatever string it returns. The replacement function can do arithmetic, look things up, or format.
The function must return a string, which is why the first example wraps the number in str(). Inside the function, m.group(), m[1] and m['n'] all work as in earlier chapters.
Escapes in the replacement string
The replacement string is itself interpreted: a backslash starts an escape. A literal backslash in the output must be doubled. When literal text containing special characters is used as the pattern, protect it with re.escape.
A function replacement avoids the escape rules altogether, because its return value is used as-is. If a substitution inserts user-supplied text, return it from a function.
Empty matches
A pattern that can match nothing replaces at every position between characters, because an empty match exists everywhere.
re.split
re.split(pattern, string, maxsplit=0, flags=0) returns a list of the pieces between matches. Unlike str.split, the separator can be a pattern.
Two things to notice. Consecutive separators produce an empty string between them ('a,b,,c'). And a separator at the very end leaves an empty string after it, as in the last line; filter these out with [p for p in parts if p] when you do not want them.
Keeping the separators
If the split pattern contains a capturing group, the text it matched is included in the result between the pieces.
Splitting at a position
Lookaround (covered in the next chapter) can split without consuming anything. As a preview, this cuts after sentence-ending punctuation but keeps it attached. The second example splits on blank lines.
| Need | Plain string method | Regex |
|---|---|---|
| Fixed text replaced | s.replace("a", "b") | Not needed |
| Varying text replaced | not possible | re.sub(pattern, repl, s) |
| Split on one fixed separator | s.split(",") | Not needed |
| Split on any run of spaces | s.split() | Not needed |
| Split on several or variable separators | not possible | re.split(pattern, s) |
re.sub and re.split when the thing you are replacing or splitting on varies.
Challenges
Given text = "Smith, Ann: 2026-11-02\nJones, Bo: 2025-01-09", use one re.sub call with named groups and re.M to turn each line into First Last: dd/mm/yyyy. Then use re.subn to count how many lines have a Last, First name at the start.
Use a replacement function to convert every Celsius reading in "Mon 20C, Tue -5C, Wed 21.5C, 3Cats" to Fahrenheit with one decimal place (for example 68.0F). Negative and decimal values must work, and 3Cats must be left alone. Say how your pattern protects it.
Split "a1+b2*(c-3)" into tokens while keeping the operators and brackets, and filter out the empty strings. Then split "Para one\nline two\n\n\nPara two\n\nPara three" into paragraphs on one or more blank lines, and show the effect of maxsplit=1.
Quick Reference
- re.sub(p, r, s, count=0): returns a new string;
re.subnalso returns the count - In the replacement:
\1,\g<1>,\g<name>insert groups - In the pattern:
\1demands a repeat of group 1 - Function replacement: receives the match, must return a string
- Literal text as a pattern:
re.escape(text) - re.split(p, s, maxsplit=0): a capturing group keeps the separators
- Empty strings: appear between adjacent separators and at the ends; filter if unwanted