From: Javier Collado on
Hello,

Let's imagine that we have a simple function that generates a
replacement for a regular expression:

def process(match):
return match.string

If we use that simple function with re.sub using a simple pattern and
a string we get the expected output:
re.sub('123', process, '123')
'123'

However, if the string passed to re.sub contains a trailing new line
character, then we get an extra new line character unexpectedly:
re.sub(r'123', process, '123\n')
'123\n\n'

If we try to get the same result using a replacement string, instead
of a function, the strange behaviour cannot be reproduced:
re.sub(r'123', '123', '123')
'123'

re.sub('123', '123', '123\n')
'123\n'

Is there any explanation for this? If I'm skipping something when
using a replacement function with re.sub, please let me know.

Best regards,
Javier
From: Steven D'Aprano on
On Tue, 06 Jul 2010 19:10:17 +0200, Javier Collado wrote:

> Hello,
>
> Let's imagine that we have a simple function that generates a
> replacement for a regular expression:
>
> def process(match):
> return match.string
>
> If we use that simple function with re.sub using a simple pattern and a
> string we get the expected output:
> re.sub('123', process, '123')
> '123'
>
> However, if the string passed to re.sub contains a trailing new line
> character, then we get an extra new line character unexpectedly:
> re.sub(r'123', process, '123\n')
> '123\n\n'

I don't know why you say it is unexpected. The regex "123" matched the
first three characters of "123\n". Those three characters are replaced by
a copy of the string you are searching "123\n", which gives "123\n\n"
exactly as expected.

Perhaps these examples might help:

>>> re.sub('W', process, 'Hello World')
'Hello Hello Worldorld'
>>> re.sub('o', process, 'Hello World')
'HellHello World WHello Worldrld'


Here's a simplified pure-Python equivalent of what you are doing:

def replace_with_match_string(target, s):
n = s.find(target)
if n != -1:
s = s[:n] + s + s[n+len(target):]
return s



> If we try to get the same result using a replacement string, instead of
> a function, the strange behaviour cannot be reproduced: re.sub(r'123',
> '123', '123')
> '123'
>
> re.sub('123', '123', '123\n')
> '123\n'

The regex "123" matches the first three characters of "123\n", which is
then replaced by "123", giving "123\n", exactly as expected.

>>> re.sub("o", "123", "Hello World")
'Hell123 W123rld'




--
Steven
From: Thomas Jollans on
On 07/06/2010 07:10 PM, Javier Collado wrote:
> Hello,
>
> Let's imagine that we have a simple function that generates a
> replacement for a regular expression:
>
> def process(match):
> return match.string
>
> If we use that simple function with re.sub using a simple pattern and
> a string we get the expected output:
> re.sub('123', process, '123')
> '123'
>
> However, if the string passed to re.sub contains a trailing new line
> character, then we get an extra new line character unexpectedly:
> re.sub(r'123', process, '123\n')
> '123\n\n'

process returns match.string, which is, according to the docs:

"""The string passed to match() or search()"""

You passed "123\n" to sub(), which may not be explicitly listed here,
but there's no difference. Process correctly returns "123\n", which is
inserted. Let me demonstrate again with a longer string:

>>> import re
>>> def process(match):
.... return match.string
....
>>> re.sub(r'\d+', process, "start,123,end")
'start,start,123,end,end'
>>>


>
> If we try to get the same result using a replacement string, instead
> of a function, the strange behaviour cannot be reproduced:
> re.sub(r'123', '123', '123')
> '123'
>
> re.sub('123', '123', '123\n')
> '123\n'

Again, the behaviour is correct: you're not asking for "whatever was
passed to sub()", but for '123', and that's what you're getting.

>
> Is there any explanation for this? If I'm skipping something when
> using a replacement function with re.sub, please let me know.

What you want is grouping:

>>> def process(match):
.... return "<<" + match.group(1) + ">>"
....
>>> re.sub(r'(\d+)', process, "start,123,end")
'start,<<123>>,end'
>>>

or better, without a function:

>>> re.sub(r'(\d+)', r'<<\1>>', "start,123,end")
'start,<<123>>,end'
>>>

Cheers,
Thomas

From: Javier Collado on
Thanks for your answers. They helped me to realize that I was
mistakenly using match.string (the whole string) when I should be
using math.group(0) (the whole match).

Best regards,
Javier