2

I have a string with unknown number of %s that need to be formatted with a single string.

For instance, if I had the string "%s some %s words %s" and wanted to format it with the word house it should output "house some house words house"

Doing the following gives me an error:

>>> "%s some %s words %s" % ("house")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: not enough arguments for format string

So, I decided to do the following, which works but seem to be overly complex for such a simple problem.

var = "house"
tup = (var,)
while True:
    try:
        print "%s some %s words %s" % tup
        break
    except:
        tup += (var,)

Is there a more pythonic way of doing this?

Moinuddin Quadri
  • 46,825
  • 13
  • 96
  • 126
Rodolfo
  • 573
  • 2
  • 8
  • 18

3 Answers3

5

If you know for sure you're subbing %s you can do it like this:

var = "house"
tup = (var,)
txt = "%s some %s words %s"

print txt % (tup * txt.count("%s"))

But a better solution is to use str.format() which uses a different syntax, but lets you specify items by number, so you can reuse them:

var = "house"
txt = "{0} some {0} words {0}"

print txt.format(var)
kindall
  • 178,883
  • 35
  • 278
  • 309
  • 2
    For the `%s` version, you might want to do something like `txt % (tup * re.findall(r'%%|%s', txt).count('%s'))` to avoid counting instances of `%s` where the `%` is escaped. – user2357112 Nov 10 '16 at 00:39
  • Yeah, if you want to really support using `%` it gets complicated pretty quickly. :-) – kindall Nov 10 '16 at 00:56
  • It is not generic as it assumes length of tuple to be 1. Check [answer](http://stackoverflow.com/a/40518447/2063361) to handle it for varying length tuples – Moinuddin Quadri Nov 10 '16 at 01:19
  • 1
    Using a variable length tuple wouldn't make much sense since the question is about how to get *one* string interpolated in multiple places in a string. – kindall Nov 10 '16 at 01:24
4

Here are a few options:

Format string (and Formatter class)

Using str.format is the most pythonic way and pretty simple to read. Either style is popular:

Position arguments

'{0} some {0} words {0}'.format('house')

Named arguments

'{word} some {word} words {word}'.format(word='house')

In a comment you mentioned preserving the original format string because of other legacy code. You could hack around that like so:

'%s some %s words %s'.replace('%s', '{0}').format('house')

(I don't recommend it but you could "short circuit" this idea line by using 'house' in the replace call instead of '{0}'.)

That said, I really think changing the template string in the first place is a better idea.

Template strings

One more alternative comes to mind after glancing at the string docs: the older string.Template class. By default it substitutes $-based values, but you can subclass it overriding the delimiter character. For example:

class MyTemplate(Template):
    """
    Overriding default to maintain compatibility with legacy code.
    """
    delimiter = '%'


t = MyTemplate('%s some %s words %s')
t.substitute(s='house')

Remember this is less common but you could write it once and re-use it every time you work with a string like this (assuming there's only one input value being substituted in). Writing it once is Pythonic at least!

Literal string interpolation

In Python 3.6, Ruby-style string interpolation is another option that the community hasn't come to a consensus on yet. For example:

s = 'house'
f'{s} some {s} words {s}'
Taylor D. Edmiston
  • 12,088
  • 6
  • 56
  • 76
1

Why not use format?

"{0} some {0} words {0}".format("house")
Def_Os
  • 5,301
  • 5
  • 34
  • 63
  • Unfortunately, I can not change the format of the string since it must be backwards compatible with the current system (I get it from a configuration file). Good solution though. – Rodolfo Nov 10 '16 at 00:40