format strings and named arguments in Python

87,329

Solution 1

Named replacement fields (the {...} parts in a format string) match against keyword arguments to the .format() method, and not positional arguments.

Keyword arguments are like keys in a dictionary; order doesn't matter, as they are matched against a name.

If you wanted to match against positional arguments, use numbers:

"{0} {1}".format(10, 20)

In Python 2.7 and up, you can omit the numbers; the {} replacement fields are then auto-numbered in order of appearance in the formatting string:

"{} {}".format(10, 20) 

The formatting string can match against both positional and keyword arguments, and can use arguments multiple times:

"{1} {ham} {0} {foo} {1}".format(10, 20, foo='bar', ham='spam')

Quoting from the format string specification:

The field_name itself begins with an arg_name that is either a number or a keyword. If it’s a number, it refers to a positional argument, and if it’s a keyword, it refers to a named keyword argument.

Emphasis mine.

If you are creating a large formatting string, it is often much more readable and maintainable to use named replacement fields, so you don't have to keep counting out the arguments and figure out what argument goes where into the resulting string.

You can also use the **keywords calling syntax to apply an existing dictionary to a format, making it easy to turn a CSV file into formatted output:

import csv

fields = ('category', 'code', 'price', 'description', 'link', 'picture', 'plans')
table_row = '''\
    <tr>
      <td><img src="{picture}"></td>
      <td><a href="{link}">{description}</a> ({price:.2f})</td>
   </tr>
'''

with open(filename, 'rb') as infile:
    reader = csv.DictReader(infile, fieldnames=fields, delimiter='\t')
    for row in reader:
        row['price'] = float(row['price'])  # needed to make `.2f` formatting work
        print table_row.format(**row)

Here, picture, link, description and price are all keys in the row dictionary, and it is much easier to see what happens when I apply the row to the formatting string.

Solution 2

Added benefits include

  • You don't have to worry about the order of the arguments. They will fall in the right place in the strings as indicated by their names in the formatter.
  • You can put the same argument in a string twice, without having to repeat the argument. E.g. "{foo} {foo}".format(foo="bar") gives 'bar bar'

Note that you can give extra arguments without causing errors as well. All this is especially useful when

  • you change the string formatter later on with less changes and thus smaller posibility for mistakes. If it does not contain new named arguments, the format function will still work without changing the arguments and put the arguments where you indicate them in the formatter.
  • you can have multiple formatter strings sharing a set of arguments. In this case you could for instance have a dictionary with the all arguments and then pick them out in the formatter as you need them.

E.g.:

>d = {"foo":"bar", "test":"case", "dead":"beef"}
>print("I need foo ({foo}) and dead ({dead})".format(**d))
>print("I need test ({test}) and foo ({foo}) and then test again ({test})".format(**d))
I need foo (bar) and dead (beef)
I need test (case) and foo (bar) and then test again (case)
Share:
87,329

Related videos on Youtube

Balakrishnan
Author by

Balakrishnan

Updated on July 05, 2022

Comments

  • Balakrishnan
    Balakrishnan almost 2 years

    Case 1:

    "{arg1} {arg2}".format(10, 20)
    

    It will give KeyError: 'arg1' because I didn't pass the named arguments.

    Case 2:

    "{arg1} {arg2}".format(arg1=10, arg2=20)
    

    Now it will work properly because I passed the named arguments. And it prints '10 20'

    Case 3:

    And, If I pass wrong name it will show KeyError: 'arg1'

    "{arg1} {arg2}".format(wrong=10, arg2=20)
    

    But,

    Case 4:

    If I pass the named arguments in wrong order

    "{arg1} {arg2}".format(arg2=10, arg1=20)
    

    It works...

    and it prints '20 10'

    My question is why does it work and what's the use of named arguments in this case.

    • svineet
      svineet over 10 years
      I think they're just for readability.
    • umbreonben
      umbreonben almost 9 years
      Because its looking it up by name rather than position...what do you think named arguments means?
    • Brad P.
      Brad P. almost 9 years
      It looks like you simply renamed arg2 to arg1 and vice versa. in other words arg1 is now 20 instead of 10, which is why you see the first number in your string print 20 instead of 10. To do the test you wanted, you needed to simply move the args AND their values to the new position in the format() call and it would behave the way you expect. Nothing is out of the ordinary here.
  • torek
    torek over 10 years
    It's not just more readable, it's also very useful when working with natural languages and "internationalization" (i18n), where you sometimes want particular parts of a formatted message to come out in different orders in different languages.
  • Martijn Pieters
    Martijn Pieters over 10 years
    @torek: I wasn't even going to go into the different orderings in the template; the point is that each slot refers to a specific argument. You can put the positional arguments in a different order in the template too.
  • starturtle
    starturtle almost 4 years
    The last example is one of the most handy approaches to templated string encoding in Python I've ever seen. In particular I wasn't aware that it's possible to hand a dictionary argument to format where not all keys actually appear in the format string. Thanks for pointing that out!