12

I have a dictionary with a colon in a key that I wish to print. Unfortunately the colon character is used for formatting, so I need to somehow escape it.

For example:

>>> d = {'hello': 'world', 'with:colon': 'moo'}

>>> '{hello}'.format(**d)
'world'

>>> '{with:colon}'.format(**d)
KeyError: 'with'

>>> '{with\:colon}'.format(**d)
KeyError: 'with\\'

>>> '{with::colon}'.format(**d)
KeyError: 'with'
gak
  • 32,061
  • 28
  • 119
  • 154

6 Answers6

10

According to the documentation, what you are asking is simply not possible. Specifically,

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.

murgatroid99
  • 19,007
  • 10
  • 60
  • 95
6

As a workaround:

>>> d = {'hello': 'world', 'with:colon': 'moo'}
>>> '{hello} {}'.format(d['with:colon'],**d)
'world moo'
>>> '{hello} {0}'.format(d['with:colon'],**d)
'world moo'
dawg
  • 98,345
  • 23
  • 131
  • 206
2

You can't - the keys must be syntactically equivalent to Python identifiers. See Format String Syntax in the documentation:

replacement_field ::=  "{" [field_name] ["!" conversion] [":" format_spec] "}"
field_name        ::=  arg_name ("." attribute_name | "[" element_index "]")*
arg_name          ::=  [identifier | integer]
attribute_name    ::=  identifier
RichieHindle
  • 272,464
  • 47
  • 358
  • 399
2

As of python 3.6 you can solve this with the new f-string formating:

>>> d = {'hello': 'world', 'with:colon': 'moo'}
>>> print(f"with:colon is equal to {d['with:colon']}")
with:colon is equal to moo
Granitosaurus
  • 20,530
  • 5
  • 57
  • 82
1

As @murgatroid99 points out in his answer, this isn't possible.

A work-around would be to replace the keys with keys that are valid:

d_sanitised = {key.replace(":", "-"): value for key, value in d.items()}

Naturally, you might want to be careful if there is the possibility of conflicts with other keys.

>>> d = {'hello': 'world', 'with:colon': 'moo'}
>>> d_sanitised = {key.replace(":", "-"): value for key, value in d.items()}
>>> '{with-colon}'.format(**d_sanitised)
'moo'

Obviously, this assumes you can modify your format strings to suit. Ideally, just modify both ends to avoid colons all together.

Community
  • 1
  • 1
Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
1

It's a pity that the built-in formatter doesn't allow that. An obvious syntax extension would be to allow keys to be quoted, if necessary. Your format string would be then like this:

format('{"with:colon"} and {hello}'

Fortunately, it appears to be easy to extend the Formatter to provide this syntax, here's a POC implementation:

class QuotableFormatter(string.Formatter):
    def __init__(self):
        self.super = super(QuotableFormatter, self)
        self.super.__init__()
        self.quotes = {}

    def parse(self, format_string):
        fs = ''
        for p in re.findall(r'(?:".+?")|(?:[^"]+)', format_string):
            if p[0] == '"':
                key = '_q_' + str(len(self.quotes))
                self.quotes[key] = p[1:-1]
                fs += key
            else:
                fs += p
        return self.super.parse(fs)

    def get_field(self, field_name, args, kwargs):
        if field_name.startswith('_q_'):
            field_name = self.quotes[field_name]
        return self.super.get_field(field_name, args, kwargs)

Usage:

d = {'hello': 'world', 'with:colon': 'moo', "weird!r:~^20": 'hi'}
print QuotableFormatter().format('{"with:colon":*>20} and {hello} and {"weird!r:~^20"}', **d)
# *****************moo and world and hi
georg
  • 211,518
  • 52
  • 313
  • 390
  • +1, nice solution - it's unfortunate it makes the syntax awkward, but if this is a common problem in the OP's code, this is by far the best solution. – Gareth Latty Nov 12 '13 at 10:35
  • @Lattyware: yeah, perhaps escaping would be nicer, like `{with\:colon}`. – georg Nov 12 '13 at 10:48