String Formatting
Up until now your programs created output using the print() function, which simply sends the output to the screen without embellishment. But sometimes you'll need your output to be formatted in a particular way. Python has some really convenient formatting features that can help with that.
To produce a formatted value or set of values, you create a string to be used as a format, then call the string's format() method. The format string can be made up of literal text (which becomes part of the formatted string) and replacement fields. The replacement fields are surrounded by curly brackets ({ }). You provide the values you want to format as arguments to the method. The values are interpolated into the format string to produce a formatted string, which becomes the result of the method call. The replacement fields contain either the position or the name of the argument whose value should be used (you'll learn more about named arguments later).
Let's look at some of the capabilities that this offers. Start an interactive session and enter the following commands to see how formatting works:
>>> "{2}, {1}, and {0}".format("George", "Paul", "John")
'John, Paul, and George'
>>> "{who} is a smart {what}".format(what='cookie', who='Sylvia')
'Sylvia is a smart cookie'
>>> "The fifth element of the first argument is {0[5]}".format(
... ["Dallas", "Zorg", "Cornelius", "Ruby", "Billy", "Leelo"])
'The fifth element of the first argument is Leelo'
>>> d = {'Cher': "Sarkisian", 'Sonny': "Bono"}
>>> "Sonny's surname is {0[Sonny]}".format(d)
"Sonny's surname is Bono"
>>> "Cher's surname is {lookup[Cher]}".format(lookup=d)
"Cher's surname is Sarkisian"
>>> for first, last in d.items():
... print("{0:10} {1:10}".format(first, last))
...
Cher Sarkisian
Sonny Bono
>>> fmt = "{0:>6} = {0:>#16b} = {0:#06x}"
>>> for i in 1, 23, 456, 7890:
... print(fmt.format(i))
...
1 = 0b1 = 0x0001
23 = 0b10111 = 0x0017
456 = 0b111001000 = 0x01c8
7890 = 0b1111011010010 = 0x1ed2
You can see that Python has some very powerful string-formatting capabilities. Now we need to understand the rules and ways to write formats that will give us the output we want. We'll go over these rules in the next few sections.
The curly brackets play a vital role in formatting strings. Each sequence of characters surrounded by a pair of curly brackets is replaced by some representation of an argument to the format() method.
| Modern Python | Everything shown here works identically on current Python. As a modern
alternative, f-strings (Python 3.6+) let you embed the values directly in the
string literal without calling .format() at all:
f"{who} is a smart {what}". F-strings are the idiomatic choice for new
code; str.format() remains fully supported and is still the right tool
when the format string itself is a variable. |
The format() method, like all Python functions, can be called with two types of argument. The first type, and the one you are most familiar with, is called positional, because it is identified by the position it occupies in the argument list. The second type is called keyword; it's preceded by a name and an equals sign.
If a call has any positional arguments, they must always appear before any keyword arguments. Thus, "...".format(a, b, k1=c, k2=d) is legal, but "...".format(k1=c, k2=d, a, b) is not (it will be flagged as a syntax error by the interpreter).
The arguments to the format() method call are the values to be formatted. The format string on which the method is called specifies how the values are to be represented, by including replacement fields. Other text in the format string (that does not appear between curly brackets) is simply copied to the output literally.
| Note | To include actual curly brackets in the output, simply put two curly brackets together, {{ or }}. These doubled curly brackets can never occur in a replacement field, and so they are treated specially. |
The first part of the replacement field, immediately following the opening curly bracket, is the field name. This tells the formatting engine which value is to be formatted. The field name begins with either a number, which specifies a positional argument to the format() method, or a name, which specifies a named argument. This can be followed by extra information that allows you to index the selected argument (which will presumably be an indexable object such as a list, tuple, or dict) or access one of its attributes.
| Example Field Name | Meaning |
|---|---|
| 1 | The second positional argument |
| name | The keyword argument called name |
| 0.attr | The attr attribute of the first positional argument |
| 2[0] | Element 0 of the third positional argument (which must be a list, tuple, or dict) |
| test[key] | The element associated with key key in the keyword argument named test |
Let's experiment now and get more comfortable programming by writing a slightly unusual program. Usually we expect to provide variable data to a program and format its results in a standard way. This time we'll provide you with standard data and let you enter format specifications that will select specific elements for display.
Type the following code as shown:
"""Accept format strings from the user and format fixed data."""
i = 42
r = 31.97
c = 2.2 + 3.3j
s = "String"
lst = ["zero", "one", "two", "three", "four", "five"]
dct = {"Jim": "Dandy",
"Stella": "DuBois",
1: "integer"}
while True:
fmt = input("Format string: ")
if not fmt:
break
fms = "{"+fmt+"}"
print("Format:", fms, "output:", fms.format(i, r, c, s, e=lst, f=dct))
Save it in your /python1 folder as formatting.py and run it; verify that you get the answers shown for the inputs given in the following table:
| Input | Output | Explanation |
|---|---|---|
| 0 | {0} : 42 | First positional argument |
| 1 | {1} : 31.97 | Second positional argument |
| 2.imag | {2.imag} : 3.3 | The imag attribute of the third positional argument |
| {{3}} | {{{3}}} : {String} | A left curly bracket (specified by {{) followed by the fourth positional argument, followed by a right curly bracket (specified by }}) |
| e[0] | {e[0]} : zero | Element zero of the keyword argument named e |
| f[Stella] | {f[Stella]} : DuBois | The element of the keyword argument named f indexed by the string "Stella" |
| f[1] | integer | The element of the keyword argument named f, indexed by the integer 1 |
To exit the program, press Enter. The program takes whatever you enter, wraps it inside curly brackets, and uses the constructed string as a formatting string against four positional arguments and two keyword arguments. The print() call will only output a single value, but you can vary the format to get all kinds of results.
The formatting mechanism has some pretty sophisticated ways to select what is formatted. Now let's see about actually formatting the selected value. We do that by following the field name with a colon and a format specification. This can include details about the filling mechanism to be used, how the output is to be aligned in the field, how to treat the signs of numbers, how wide the field should be, how many digits of precision to allow, or what type of conversion should be performed on the selected value.
The various components of the format specification must appear in a prescribed order. No component is required.
Padding clears an area around the content (inside the border) of an element. You don't need to specify a padding character, but if you do specify padding, you must specify the field's alignment as well. There are four different characters that you can use to specify the field's alignment. If the alignment specifier is preceded by some other character, that character is used to pad the field to the requested width; otherwise the space character is used. The alignment options are:
| Alignment Option | Meaning |
|---|---|
| < | The field is left-aligned in the available space, with any padding to its right. This is the default when no alignment is specified. |
| > | The field is right-aligned in the available space, with any padding to its left. |
| = | (Valid only for numeric types). Forces the padding to be placed after the sign but before any digits. This can be used to print padded numeric values with the signs all aligned above each other. Pad characters are typically "0" or "*". |
| ^ | The field is centered within the available space. Padding characters will be added on the left and right. |
No padding is required if the value occupies the whole width of the field. If no width is specified, this will always be the case, and no padding will ever be inserted.
As you may have guessed, we don't specify signs for non-numeric values. The interpreter would raise a ValueError exception if it found such a sign specification. There are three ways we can use signs:
| Option | Meaning |
|---|---|
| + | Insert a + sign for positive values, a - sign for negative values. |
| - | Insert a - sign for negative values, no sign for positive values. |
| space | Insert a - sign for negative values, a space for positive values |
The base indication can only be requested for integers whose values are being displayed in hexadecimal, octal, or binary. To request it, include a hash mark (#) in the format specification. When a base indicator is requested, binary numbers are preceded by 0b, octal numbers by 0o and hexadecimal numbers by 0x.
To use commas as thousands separators (for example, 9,999,999), insert a comma in the format specification. This may restrict your programs' portability, as some locales use a comma as a decimal point and a period as a thousands separator. To keep your code as portable as possible, use locale-dependent types of specifications (more on this in a few minutes).
The field width is a decimal integer specifying the total width of the output generated by the format specifier. As a special case, if the field width begins with a zero character ('0'), it is treated as a shorthand for a pad character of '0' and a fill type of '=' (zeroes between the sign and the digits). This is illegal for non-numeric values and will raise a ValueError exception under those circumstances.
Precision is specified as a period followed by a decimal number. For numeric values, this indicates how many significant digits to display:
>>> "{0:15.5}".format(987.654)
' 987.65'
>>> "{0:15.5}".format(98765.432)
' 9.8765e+04'
For other types of values, it indicates how many characters will be used from the field content.
Last of all comes a letter that dictates which type of value should be formatted. For string values, the letter can be omitted, or can be s. All numeric types can also be formatted with a field type of s, in which case the resulting value before alignment and truncation (limiting the number of digits to the right of the decimal point) is the same as that produced by applying the built-in str() conversion. Complex number values cannot be formatted in the same way as real and integer values; instead, you must format the real and imaginary parts separately. You can access these parts using the .real and .imag attribute qualifiers in the field names. Integer and long values can be formatted with these field types:
| Type | Field Type |
|---|---|
| b | Binary: formats the number in base 2. |
| c | Character: converts the number to the corresponding Unicode character. |
| d | Decimal: formats the integer in base 10. |
| o | Octal: formats the integer in base 8. |
| x | Hexadecimal: formats the number in base 16, using lower-case letters a through f for the digits from 10 to 15. |
| X | Hexadecimal: like x, but uses upper-case letters. |
| n | Like d, but uses the locale settings to determine the decimal point and thousands separator characters. |
| No code | Treated the same as d. |
Floating-point and decimal values use a separate set of type codes:
| Type | Field Type |
|---|---|
| e | Exponential notation: formats in scientific notation using e to indicate the exponent. |
| E | Same as e but uses an upper-case exponent indicator. |
| f | Fixed-point. Displays the number as a fixed-point number, using "nan" to represent "not a number" and "inf" to represent infinity. |
| F | Same as f but upper-case: uses "NAN" and "INF." |
| g | General format. Uses fixed-point format unless the number is too large, in which case it uses exponent notation with lower-case indicators. |
| G | Like g but uses upper-case indicators. |
| n | Like g but uses the current locale settings to determine decimal point and thousands separators. |
| % | Multiplies the number by 100 and displays in f format followed by a percent sign. |
| No code | Treated similarly to g except that it always produces at least one digit after the decimal point and by default uses a precision of 12. |
The field width and the precision are numeric values. If you want these values to be reliant on program data, you can pass the width and precision as arguments to the format() method and then use a nested field name inside the format specification. This nested field name (which must refer to an integer value) is substituted for the field width or precision as the formatting takes place. So, for example, "{0:{1}.{2}f}".format(1234.5678, 18, 3) displays the number 1234.5678 to three decimal places in a field 18 characters wide.
Let's try a few examples. Start up an interactive session and enter the commands shown:
>>> "{0:010.4f}".format(-123.456)
'-0123.4560'
>>> "{0:+010.4f}".format(-123.456)
'-0123.4560'
>>> for i in 1, 2, 3, 4, 5:
... "{0:10.{1}f}".format(123.456, i)
...
' 123.5'
' 123.46'
' 123.456'
' 123.4560'
' 123.45600'
>>> n = {'value': 987.654, 'width': 15, 'precision': 5}
>>> "{0[value]:{0[width]}.{0[precision]}}".format(n)
' 987.65'
The numerical rounding is always correct. And by using dict access, you can carry the value, field width, and precision (along with other values you might need) all within a single object.
| Modern Python | F-strings support the same nested expressions for dynamic width and precision:
f"{-123.456:{10}.{4}f}" gives '-0123.4560' (using variables for the
width and precision). The syntax is f"{value:{width}.{precision}f}" where
width and precision are any integer-valued expressions. |
This example program lists the names, ages, and weights of a number of individuals. Currently the data is stored as a list of tuples. We'll list the data using formatting statements. Enter this code in the editor window:
"""Produce a listing of people's names, ages and weights."""
data = [
("Steve", 59, 202),
("Dorothy", 49, 156),
("Simon", 39, 155),
("David", 61, 135)]
for row in data:
print("{0[0]:<12s} {0[1]:4d} {0[2]:4d}".format(row))
Save it in your /python1 folder as personlist.py and run it. While this program works, the correspondence between related data items seems a little obscure. Modify the program as shown below to extract the individual items from the row and pass them as separate arguments to the format() call:
"""Produce a listing of people's names, ages and weights."""
data = [
("Steve", 59, 202),
("Dorothy", 49, 156),
("Simon", 39, 155),
("David", 61, 135)]
for roname, age, weight in data:
print("{0[0]:<12s} {0[1]:4d} {0[2]:4d}".format(roname, age, weight))
Save and run it again. The results are the same, but which code do you think is easier to read? Readability is incredibly important in software: the easier code is to read the easier it is to understand. This applies not only to other readers, but also to you. When you return to a program after a considerable length of time, you don't want it to be a puzzle to understand.
Okay, now let's make the name field wider. We'll use the period as a pad character to help the reader follow the line from the name to the age and weight. Modify the program a third time—add a padding character before the alignment indication and increase the field width:
"""Produce a listing of people's names, ages and weights."""
data = [
("Steve", 59, 202),
("Dorothy", 49, 156),
("Simon", 39, 155),
("David", 61, 135)]
for name, age, weight in data:
print("{0:.<1230s} {1:4d} {2:4d}".format(name, age, weight))
Save and run it again.
| Modern Python | The f-string equivalent of the final version is simply
print(f"{name:.. The format
mini-language inside { } is identical between f-strings and
str.format(), so everything you've learned here applies
directly. |
You've learned so much about Python's formatting features! They can really help make the output from your programs readable and usable.
In the next lesson, we'll return to functions and talk about the role of keyword arguments and parameters like the ones we used with the string format() method in this lesson.
See you there!
