You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Escaping the repl argument to re.sub(), re.subn() #128138
It's not immediately obvious how to escape the repl (replacement for matches) argument to re.sub() and re.subn() if repl is chosen by a potentially hostile actor. Obviously, re.escape() isn't the answer, as that escapes far too much.
The right answer seems to be escaped_repl = raw_repl.replace(bslash, bslash*2) where bslash = '\\'. It might be worth adding this to the documentation.
Here's the code I used to empirically validate the "right answer" given above (checked on Python 3.8 & 3.12):
Obviously, re.escape() isn't the answer, as that escapes far too much.
It was not obvious to me whether escaping "too much" is harmful, but in this case yes.
For example it escapes various whitespace re.escape(r' ') == r'\ ', which is harmless for pattern, but in repl results in the backslash staying in the result due to the rule "Other unknown escapes such as \& are left alone".
But first, what's your exact goal? Which of the repl abilities do you want to expose vs. block?
\1 group references?
\g<...> group references?
Regular escapes like in string-literals \n, \t etc. (sub() supports these so you can use raw string literal for repl)?
A way to express a literal backslash in the replacement.
Do you want verbatim replacement strings — no dynamic references, and your API receives the replacement 1:1, with single backslash meaning single backslash in the result?
In that case I think .replace('\\', '\\\\') works, with all backslashes doubled no other escape sequences can be recognized ✅.
But consider instead giving a replfunction, this passes your test too ✅:
forrawintest_cases:
defrepl(match):
returnraw# ^ fine as we call it in same loop iteration.# If you needed a long-lived closure, could `def repl(match, raw=raw):`.got, change_count=re.subn(base_regex, repl, base_input)
Don't think of passing a function is "advanced last-resort escape hatch", it's actually mentally simpler than the string repl notation! Whatever the function returns is emitted 1:1.
Activity
cben commentedon Feb 8, 2026
It was not obvious to me whether escaping "too much" is harmful, but in this case yes.
For example it escapes various whitespace
re.escape(r' ') == r'\ ', which is harmless for pattern, but in repl results in the backslash staying in the result due to the rule "Other unknown escapes such as\&are left alone".But first, what's your exact goal? Which of the repl abilities do you want to expose vs. block?
\1group references?\g<...>group references?\n,\tetc. (sub()supports these so you can use raw string literal for repl)?Do you want verbatim replacement strings — no dynamic references, and your API receives the replacement 1:1, with single backslash meaning single backslash in the result?
In that case I think
.replace('\\', '\\\\')works, with all backslashes doubled no other escape sequences can be recognized ✅.But consider instead giving a repl function, this passes your test too ✅:
Don't think of passing a function is "advanced last-resort escape hatch", it's actually mentally simpler than the string repl notation! Whatever the function returns is emitted 1:1.
cben commentedon Feb 8, 2026
Oh, I see re.escape doc already explains this since #3907 (emphasis mine):
@finite-state-machine do you feel this can be closed? If not, please suggest concrete doc improvement.
Restructure re.sub docs, clarify aspects of `repl` notation
Restructure re.sub docs, clarify aspects of `repl` notation
Restructure re.sub docs, clarify aspects of `repl` notation
Restructure re.sub docs, clarify aspects of `repl` notation
Restructure re.sub docs, clarify aspects of `repl` notation