18

I would like to use Python string's format() to act as a quick and dirty template. However, the dict that I would like to use has keys which are (string representations) of integers. a simplified example follows:

s = 'hello there {5}'
d = {'5': 'you'}
s.format(**d)

the above code throws the following error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: tuple index out of range

is it possible to do the above?

2
  • 5
    Just a note, don't use str as a variable name as it will override the builtin str class. Commented Dec 19, 2013 at 9:25
  • 1
    {[5]} would work if the key was actually an integer. Commented Dec 19, 2013 at 9:30

6 Answers 6

28

We've established that it won't work, but how about a solution:

Although str.format won't work in this case, funnily enough the old % formatting will. This is not recommended, but you did ask for a quick and dirty template.

>>> 'hello there %(5)s' % {'5': 'you'}
'hello there you'

Do note though that this won't work for integer keys.

>>> 'hello there %(5)s' % {5: 'you'}

Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    'hello there %(5)s' % {5: 'you'}
KeyError: '5'
Sign up to request clarification or add additional context in comments.

Comments

8

I love the idea of extending the Formatter so that it allows arbitrary field names (integers, field names with a colon etc). An implementation might look like this:

import string, re

class QuFormatter(string.Formatter):
    def _quote(self, m):
        if not hasattr(self, 'quoted'):
            self.quoted = {}
        key = '__q__' + str(len(self.quoted))
        self.quoted[key] = m.group(2)
        return '{' + m.group(1) + key + m.group(3) + '}'

    def parse(self, format_string):
        return string.Formatter.parse(self,
            re.sub(r'{([^}`]*)`([^}`]*)`([^}]*)}', self._quote, format_string))

    def get_value(self, key, args, kwargs):
        if key.startswith('__q__'):
            key = self.quoted[key]
        return string.Formatter.get_value(self, key, args, kwargs)

Usage:

d = {'5': 'you', '6': 'me', "okay":1, "weird:thing!": 123456}
print QuFormatter().format(
     'hello there {`5`} {`6`:20s}--{okay}--{`weird:thing!`:20,d}', 
     **d)

So fields in backticks are treated literally.

Comments

7

See this post for answers to your problems. It seems that you cannot use strings consisting of numbers as dictionary keys in format strings (docs link).

If you can use a key other than 5 then it will work:

my_string='hello there {spam:s}'
d={'spam': 'you'}
print my_string.format(**d) # Returns "hello there you"

1 Comment

The most important part of the linked answer is the quote from the docs: Because arg_name is not quote-delimited, it is not possible to specify arbitrary dictionary keys (e.g., the strings '10' or ':-]') within a format string.
3

From PEP 3101

The built-in string class (and also the unicode class in 2.6) will gain a new method, 'format', which takes an arbitrary number of positional and keyword arguments:

"The story of {0}, {1}, and {c}".format(a, b, c=d)

Within a format string, each positional argument is identified with a number, starting from zero, so in the above example, 'a' is argument 0 and 'b' is argument 1. Each keyword argument is identified by its keyword name, so in the above example, 'c' is used to refer to the third argument.

Numeric values used in str.format are positional arguments. So you can not do that.

You can reach PEP 3101 from here. Related section is is under String Methods

As @Volatility mentioned, you can use % formatter for this.

Comments

1

You could do something with get_value in a custom string.Formatter to try replacement fields as dictionary keys before falling back on index into arg keys - note the possible conflict of priority and intent here... so it's not exactly recommended, but an idea of what's possible:

import string

class MyFormatter(string.Formatter):
    def get_value(self, key, args, kwargs):
        try:
            return kwargs[str(key)]
        except KeyError:
            return super(MyFormatter, self).get_value(key, args, kwargs)

s = 'hello there {5} - you are number {0}'
d = {'5': 'you'}
print MyFormatter().format(s, 1, 2, 3, **d)
# hello there you - you are number 1

Comments

0

It is actually possible, using the fact {k} seeks for the (k+1)th positional argument.

def populate_list(d):
   """ Return a list l verifying l[k] = d[str(k)] for each natural k """
   return [d.get(str(k)) for k in range(1 + max(map(int, d)))] if d else []

def format_with_int_keys(s,d):
   """ Replace each {k} in s by d[str(k)] """
   return s.format(*populate_list(d))

s = 'hello there {5}'
d = {'5': 'you'}
print (format_with_int_keys(s,d))

Edit: It's actually a detailed version of @wim solution.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.