re.sub

str.replace with patterns. The replacement is a small template language — \1 and \g<name> insert groups — or a function that computes each replacement. re.subn does the same and also returns how many replacements were made; both exist as Pattern.sub / Pattern.subn.

re functionPython 3 (all)Live demo
Common call
re.sub(r'\s+', ' ', text)
Returns
a new str (subn: a (str, count) tuple)
Replaces
chains of str.replace calls, manual rebuilding with slices
Watch out
Backslashes in repl are special too: write it as a raw string, and \d there is an error
re.sub(patternpattern — What to replace, as a raw-string regular expression.type: str | re.Pattern · required, replrepl — Template string (\1, \g<name>, \g<0>, \n …) or a function taking the Match and returning a str.type: str | callable · required, stringstring — The input text (not modified — strings are immutable).type: str · required, countcount — Maximum number of replacements; 0 means all. Pass it by keyword (positional is deprecated since 3.13).type: int · default: 0=0, flagsflags — Flags such as re.IGNORECASE. Pass by keyword.type: re.RegexFlag | int · default: 0=0)
→ str

Demo

Live evaluation
Every match replaced. In repl, \1 is group 1, \g<name> a named group, \g<0> the whole match.
Try:
Inputs
patternstra regular expression
replstrreplacement template
textstrinput text
Code
import re
re.sub('(\\w+), (\\w+)', '\\2 \\1', 'Lovelace, Ada')
Result
'Ada Lovelace'

\g<1>0 is the way to write "group 1, then the digit 0" — \10 would mean group 10. A group that did not take part in a match is replaced by an empty string (since Python 3.5). In the empty-match subn case, x* matches the empty string at every position, so a dash lands between every character and at both ends. Errors report positions inside repl, counted from 0.

Parameters

NameTypeRequiredDescription
patternstr | re.PatternyesWhat to replace, as a raw-string regular expression.
replstr | callableyesTemplate string (\1, \g<name>, \g<0>, \n …) or a function taking the Match and returning a str.
stringstryesThe input text (not modified — strings are immutable).
countintno (0)Maximum number of replacements; 0 means all. Pass it by keyword (positional is deprecated since 3.13).
flagsre.RegexFlag | intno (0)Flags such as re.IGNORECASE. Pass by keyword.

Return value

str — A new string with the matches replaced (subn returns a (new_string, number_of_replacements) tuple).

Common patterns

Normalize whitespace
Collapse every run of spaces, tabs and newlines into one space.
import re
clean = re.sub(r'\s+', ' ', text).strip()
Replace from a dict
Escape the keys, join them into one alternation, look each match up.
import re
repl = {'cat': 'dog', 'red': 'blue'}
pat = re.compile('|'.join(map(re.escape, repl)))
result = pat.sub(lambda m: repl[m.group()], text)
Insert user text literally
A function return value is not parsed as a template — safe for backslashes.
import re
result = re.sub(r'\{name\}', lambda m: user_input, template)
Count replacements
subn tells you whether anything changed.
import re
new_text, n = re.subn(r'\bcolour\b', 'color', text)
if n:
    print(f'fixed {n} spellings')

Examples

1. Reorder with groups
import re re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', 'due 2026-09-29')
Returns
'due 29/09/2026'
2. Named groups in repl
import re re.sub(r'(?P<y>\d{4})-(?P<m>\d{2})', r'\g<m>/\g<y>', '2026-09')
Returns
'09/2026'
3. The whole match: \g<0>
import re re.sub(r'\d+', r'<\g<0>>', 'a1b22')
Returns
'a<1>b<22>'
4. A function as repl
import re re.sub(r'\d+', lambda m: str(int(m.group()) * 2), 'a1b22')
Returns
'a2b44'
5. Limit with count
import re re.sub(r'a', 'x', 'aaaa', count=2)
Returns
'xxaa'
6. subn counts
import re re.subn(r'a', 'x', 'banana')
Returns
('bxnxnx', 3)
7. Unmatched group becomes empty
import re re.sub(r'(a)|b', r'[\1]', 'ab')
Returns
'[a][]'
8. Compiled: Pattern.sub
import re re.compile(r'\s+').sub('_', 'a b\tc')
Returns
'a_b_c'

Pitfalls

1. Referring to a group that does not exist
The template is checked against the pattern: \2 needs two groups. The position counts inside repl.
r'\2'
import re
re.sub(r'(a)', r'\2', 'a')
re.PatternError: invalid group reference 2 at position 1
r'\1'
import re
re.sub(r'(a)', r'\1\1', 'a')
'aa'
2. Backslashes from data in repl
A string repl is a template, so a Windows path or user text with \d breaks. Return it from a function (not parsed) or escape backslashes.
path as repl
import re
path = r'C:\data'
re.sub(r'DIR', path, 'cd DIR')
re.PatternError: bad escape \d at position 2
lambda
import re
path = r'C:\data'
re.sub(r'DIR', lambda m: path, 'cd DIR')
'cd C:\\data'
3. Group number followed by a digit
\10 means group 10, not group 1 then 0. \g<1> ends the reference explicitly.
r'\10'
import re
re.sub(r'(a)', r'\10', 'a')
re.PatternError: invalid group reference 10 at position 1
r'\g<1>0'
import re
re.sub(r'(a)', r'\g<1>0', 'a')
'a0'
4. An unknown group name
A misspelled \g<name> raises IndexError, not re.error.
typo
import re
re.sub(r'(?P<word>\w+)', r'\g<wrod>', 'hi')
IndexError: unknown group name 'wrod'
real name
import re
re.sub(r'(?P<word>\w+)', r'<\g<word>>', 'hi')
'<hi>'

When to use

Use it
  • Replacing text that varies: numbers, whitespace runs, dates, names
  • Rearranging parts of each match with groups
  • Computing each replacement with a function
Reach for something else
  • Fixed text → str.replace (faster, no escaping rules)
  • Character-for-character mapping → str.translate
  • Several unrelated fixed replacements → one compiled alternation + dict lookup (see patterns)

Notes

CPython impl
pattern_subx in Modules/_sre/_sre.c. A repl string without any backslash is used as-is; otherwise re._compile_template parses it once (cached) into literal parts and group indexes
Template escapes
In repl: \n \t \r \f \v \a \b \\ are processed, \0 and three-digit octal escapes give characters, other escaped non-letters stay as written, an escaped ASCII letter is an error (3.7+)
Empty matches
Since 3.7 empty matches adjacent to a previous non-empty match are replaced too: re.sub("x*", "-", "abxd") gives "-a-b--d-"
Arguments
Passing count or flags positionally emits a DeprecationWarning since 3.13 — use count= and flags=

FAQ

Write \1, \2 … or \g<name> for named groups in a raw-string repl: re.sub(r'(\w+) (\w+)', r'\2 \1', 'Ada Lovelace') gives 'Lovelace Ada'. \g<0> is the whole match.