Re.sub Replacement Starts With Digits: Check Group References and Silent Octal Output
By ON Lab · Sources checked November 8, 2026
Concatenating \1 with replacement data beginning in a digit can change the replacement template’s meaning. Delimit references with \g<1>. If the value can contain arbitrary backslashes, return it from a function rather than treating it as template syntax.
Reproduce errors and silent wrong results
The historical user question replaced a hostname inside a placeholder such as ${9:example.com}. Some digit-prefixed values raised an invalid-group-reference error while others appeared to work. Our smaller fixture used two captured boundary groups:
import re
pattern = r'(\$\{\d{1,2}:)example\.com(\})'
text = '${1:example.com}'
value = '8.8.8.8'
replacement = r'\1' + value + r'\2'
print(repr(replacement))
print(re.sub(pattern, replacement, text))Python 3.9.6 interpreted the beginning \18 as a reference to group 18, but only two capture groups existed. No exception is not a sufficient success check:
| Inserted value | Numbered-template result |
|---|---|
| 1 | Invalid group reference 11 |
| 12.example.org | J.example.org} |
| 127.0.0.1 | J7.0.0.1} |
The last two silently lost the intended prefix. In our run the three-digit escape \112 was interpreted as octal and produced J. The original report’s “works” distinction therefore needs a complete output check, not only an exception check.
Delimit the group reference
import re
pattern = r'(\$\{\d{1,2}:)example\.com(\})'
text = '${1:example.com}'
value = '12.example.org'
result = re.sub(pattern, r'\g<1>' + value + r'\g<2>', text)
assert result == '${1:12.example.org}'
print(result)This correctly preserved the placeholder boundaries for our numeric values. Delimiting a group number does not, however, make the inserted value literal: the combined replacement is still parsed as a template.
Return arbitrary data from a function
import re
pattern = r'(\$\{\d{1,2}:)example\.com(\})'
text = '${1:example.com}'
value = r'C:\new\1'
result = re.sub(pattern, lambda match:
match.group(1) + value + match.group(2), text)
assert result == '${1:' + value + '}'
print(repr(result))Our callback preserved that value’s backslashes. The delimited string template did not: its \n and \1 were still interpreted. A function’s returned string is not parsed again as a replacement template. Validate the value if required, and return the expected text type.
Do not use re.escape as a general replacement repair. It escapes text for a regex pattern. A Python string literal, a pattern and a replacement template are different parsing layers. A raw string avoids Python’s ordinary escape processing; it does not disable replacement-template parsing.
Check group existence and arguments separately
An invalid reference can also name a group that the pattern never captured. Inspect the compiled pattern’s groups and groupindex, and distinguish capturing from noncapturing groups. Delimiters cannot create a nonexistent group.
The historical question also passed a flag positionally in the count slot. Current code can use an explicit keyword such as flags=re.MULTILINE when required. Our fixture needed no multiline flag. Correcting count or flags does not fix a digit attached to a backreference.
Verify the complete result
- Use a synthetic string with the exact placeholder structure.
- Inspect the replacement with repr before substitution.
- Test a letter prefix, one digit, an octal-looking prefix and a backslash-containing value.
- Compare the entire result, including both preserved boundaries.
- Check the substitution count if exactly one match is required.
The standard library and original public answer are free alternatives. This guide adds silent-octal and backslash counterexamples to the usual delimited-reference fix. No external document, mail message or production configuration was modified.
Method: Python 3.13 regex documentation, a historical public question and 21 synthetic Python 3.9.6/macOS substitutions. An initial test expected one prefix to raise; the actual silent wrong output was retained and investigated before drafting. No other regex engine or original application was reproduced.