How do I escape a colon in Python format() when us

2019-06-15 09:01发布

问题:

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'

回答1:

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'


回答2:

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.



回答3:

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


回答4:

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


回答5:

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.



回答6:

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