Replacing with sub

re · Practical · 10 min

Normalising data, masking secrets and reformatting dates are all pattern-based replacements.

re.sub(pattern, repl, text) replaces every match with repl and returns the new string. count=1 limits replacements; re.subn also returns how many were made.

The replacement string can refer to groups:

  • \1, \2 insert the text of numbered groups (use a raw string: r"\2/\1").
  • \g<name> inserts a named group, and \g<1> avoids ambiguity before digits (\g<1>0 vs \10).
  • Other backslash escapes in repl are processed too, so a literal backslash must be written \\.

Instead of a string, repl can be a function that receives the Match object and returns the replacement. Use it for logic: converting units, looking up values in a dict, changing case:

re.sub(r"\d+", lambda m: str(int(m[0]) * 2), "3 apples")

When the replacement text comes from users or data, either escape backslashes or use a function returning the text, so sequences like \1 in the data aren't interpreted.

Since 3.13, count and flags must be passed as keywords; positional use is deprecated (re.sub(p, r, s, re.I) used to silently mean count=2).

For a fixed substring, str.replace is simpler and faster.

Example

import re

dates = "from 03/01/2025 to 12/31/2025"
print(re.sub(r"(\d{2})/(\d{2})/(\d{4})", r"\3-\1-\2", dates))
# from 2025-03-01 to 2025-12-31

print(re.sub(r"(?P<user>\w+)@(\w+)\.com", r"\g<user>@***", "mail ann@corp.com"))
# mail ann@***

prices = {"apple": 0.5, "pear": 0.75}
order = "3 apple, 2 pear"
total = re.sub(r"(\d+) (\w+)", lambda m: f"{int(m[1]) * prices[m[2]]:.2f}", order)
print(total)                                           # 1.50, 1.50

text, n = re.subn(r"\s+", " ", "too   many \t spaces")
print(text, n)                                         # too many spaces 2
print(re.sub(r"x", "y", "xxx", count=1))               # yxx

Remember

  • \1 and \g<name> insert groups in the replacement; use raw strings
  • A function as repl computes each replacement from the Match
  • Pass count= and flags= by keyword (positional is deprecated in 3.13)

Watch out for

  • Inserting user data as the replacement string, where backslashes are interpreted as group references.

Check yourself

  1. What does re.sub(r'(\w+) (\w+)', r'\2 \1', 'hello world') return?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/re.html#re.sub