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,\2insert 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>0vs\10).- Other backslash escapes in
replare 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)) # yxxRemember
\1and\g<name>insert groups in the replacement; use raw strings- A function as
replcomputes each replacement from the Match - Pass
count=andflags=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.