How to turn %s into {0}, {1} ... less clunky?

Viewed 361

I have to take a string containing placeholders for later substitution, like:

"A %s B %s"

And turn that into:

"A {0} B {1}"

I came up with:

def _fix_substitution_parms(raw_message):
  rv = raw_message
  counter = 0
  while '%s' in rv:
    rv = rv.replace('%s', '{' + str(counter) + '}', 1)
    counter = counter + 1
return rv

That works, but it feels super clunky, and not at all "idiomatic" python.

How would a well, idiomatic python solution look like?

Updates for clarification:

  • the resulting strings aren't used within python. I do need the counter numbers in there! (so {} isn't good enough)!
  • I only need to care about %s strings, as the messages are guaranteed to only use %s (no %i %f whatsoever)
6 Answers

Use re.sub with a lambda function for reapplying the substitution once for each element, and itertools.count for getting numbers sequentially:

import itertools
import re

s = "A %s B %s"

counter = itertools.count()
result = re.sub('%s', lambda x: f'{{{next(counter)}}}', s)
print(result)  # 'A {0} B {1}'

Remember to wrap this in a function to perform this operation more than once, since you'll need to refresh itertools.count.

I would do what Reznik originally suggested and then call .format on that:

def _fix_substitution_parms(raw_message: str) -> str:
    num_to_replace = raw_message.count("%s")
    python_format_string_message = raw_message.replace("%s", "{{{}}}")
    final_message = python_format_string_message.format(*range(num_to_replace))
    return final_message

I think that shoudl work

rv.replace('%s','{{{}}}').format(*range(rv.count('%s')))

Using re.sub for dynamic replacing:

import re

text = "A %s B %s %s B %s"


def _fix_substitution_parms(raw_message):
    counter = 0
    def replace(_):
        nonlocal counter
        counter += 1
        return '{{{}}}'.format(counter - 1)
    return re.sub('%s', replace, raw_message)


print(_fix_substitution_parms(text))  # A {0} B {1} {2} B {3}

Using a generator:

def split_and_insert(mystring):
    parts = iter(mystring.split('%s'))
    yield next(parts)
    for n, part in enumerate(parts):
        yield f'{{{n}}}'
        yield part

new_string = ''.join(split_and_insert("A %s B %s"))

Have you try with .format?

string.format(0=value, 1=value)

So:

"A {0} B {1}".format(0=value, 1=value)

Related