简体   繁体   中英

The use of punctuation in python format strings

So I'm confused by the .format mechanism in python. (I'm currently using 2.7.6)

So, this obviously works:

>>> "hello {test1}".format(**{'test1': 'world'})
'hello world'

and so does:

>>> "hello {test_1}".format(**{'test_1': 'world'})
'hello world'

but neither:

>>> "hello {test:1}".format(**{'test:1': 'world'})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'test'

nor:

>>> "hello {test.1}".format(**{'test.1': 'world'})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'test'

work.

But for some reason the following does:

>>> "hello {test:1}".format(**{'test': 'world'})
'hello world'

So it seems that variable names in the string being replaced can not have colons : or periods . in them. Is there any way to escape these characters? The strings I am looking to replace from a dictionary occasionally have either periods or both periods or colons.

This is because you can use the format mini language to access attributes of objects. For instance, I use this often in my own work on custom classes. Say I've defined a class for each computer I need to work on.

class Computer(object):
    def __init__(self,IP):
        self.IP = IP

And now I want to do something to a whole RANGE of computers

list_comps = [Computer(name,"192.168.1.{}".format(IP)) for IP in range(12)]

for comp in list_comps:
    frobnicate(comp) # do something to it
    print("Frobnicating the computer located at {comp.IP}".format(comp=comp))

Now it will print out

Frobnicating the computer located at 192.168.1.0
Frobnicating the computer located at 192.168.1.1
Frobnicating the computer located at 192.168.1.2 # etc etc

because each time, it finds the object I'm passing to the formatter ( comp ), grabbing its attribute IP , and using that instead. In your examples, you're giving the formatter something that looks like an attribute accessor ( . ) so it tries to access the object given BEFORE the accessor, then look for its defined attribute.

Your final example works because it's looking for test , and it found it! The : symbol is special to the formatter, as it marks the end of the kwarg and the beginning of the format mini-language. For instance:

>>> x = 12.34567
>>> print("{x:.2f}".format(x))
12.34

The .2f after the : tells the string formatter to treat the parameter x as a float and truncate it after 2 digits past the decimal point. This is well documented and I strongly suggest you take a good long look at this and bookmark it for future use! It's quite helpful!

What @adsmith didn't mention is that the format names must be valid python identifiers. Hence you can include underscores and digits, but you can't include a (quoted) colon because it cannot be part of an identifier.

What can you do? I would look into renaming your dictionary objects, or adapting your format strings to use positional arguments.

If these are impractical due to the nature of your data, or if you just want to get your way on this, the Formatter class is your friend. It allows you to override python's template string parser with your own parse() method. Since you don't actually seem to need a format specification inside the braces (like {test:6d} to pad something to 6 spaces), all you have to do is construct a parse method that treats the entire format string as the field name, with empty format_spec and conversion .

Edit: This seemed like a fun thing to try, so here's a simple working implementation.

import string

class myFormatter(string.Formatter):
    def parse(self, fstring):
        if fstring is None:  # we also get called with the (null) format spec, for some reason
            return
        parts = fstring.split("}")
        for part in parts:
            if "{" in part:
                literal, fieldname = part.split("{")
                yield (literal, fieldname, None, None)
            else:
                yield (part, None, None, None)

Use it like this:

>>> custom = myFormatter()
>>> custom.format("hello {test:1}", [], **{'test:1': 'world'})
'hello world'

Or better yet like this:

print custom.vformat(templatestring, [], valuedict)

Well, did You check documentation ?

replacement_field ::=  "{" [field_name] ["!" conversion] [":" format_spec] "}"
field_name        ::=  arg_name ("." attribute_name | "[" element_index "]")*

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM