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'
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'
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.
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
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
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.
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