Desk Clarity

Everyday document & browser help

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 valueNumbered-template result
1Invalid group reference 11
12.example.orgJ.example.org}
127.0.0.1J7.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

  1. Use a synthetic string with the exact placeholder structure.
  2. Inspect the replacement with repr before substitution.
  3. Test a letter prefix, one digit, an octal-looking prefix and a backslash-containing value.
  4. Compare the entire result, including both preserved boundaries.
  5. 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.

Sources