Embercode
✕
StringsLesson 14 of 16

45 min read

Formatting output in Python

Why it matters

A program that prints 3.14159265 next to Tea is correct, but nobody wants to read it. A receipt, a table of scores or a progress line needs its columns to line up and its numbers to show the same number of decimal places. Printing text in a fixed shape is called formatting. Formatting is the difference between output a person can scan and output that only a computer finds easy to read.

Python handles formatting with a format spec. A format spec is a short piece of text after a colon inside braces, such as {3.14159:.2f}. The part before the colon is the value, and the part after the colon says how to show it: the width, the alignment, the number of decimal places and so on. You can write the spec inside an f-string, which is a string with an f before the opening quote, or you can use str.format with the same specs.

This lesson covers f-strings and str.format first, then the older % operator, which you will still see in code written before f-strings existed. It also covers the rules that surprise beginners, such as rounding. Reading text from a user or splitting a line into fields is not covered here. The CSV splitting lesson in this module covers that.

The idea: a template with holes

Think of a form letter. The text "Dear NAME, your balance is AMOUNT." stays the same for every customer. Only the two holes change. An f-string works the same way. Here is the smallest useful example.

Python · editable

The output is:

Dear Ada, your balance is 42.50.

The braces {name} put the value in place. The part {balance:.2f} has a spec, .2f, which means a decimal number with exactly two digits after the decimal point. The f at the end of the spec is the type, and it says the value is a float, which is Python's name for a decimal number.

A spec has up to five parts, in this order: a fill character, an alignment (< for left, > for right, ^ for centre), a sign (+ or -), a width such as 10, and a precision such as .3. The type letter comes last. So {3.14159:-^10.3f} means: fill with -, centre the value, give it a width of 10, show three decimal places.

These are the types beginners use most.

SpecMeaningExample output
dwhole number (integer)42 for the value 42
fdecimal number, 6 digits after the point by default3.141590 for 3.14159
stextAda
xwhole number in hexadecimal (base 16)ff for 255
owhole number in octal (base 8)10 for 8
bwhole number in binary (base 2)101 for 5
escientific notation (a number times a power of 10)1.234568e+04 for 12345.678
,thousands separator, placed before the type1,234,567 for 1234567
%a number times 100 with a percent sign45.7% for 0.4567 with .1%

With no type letter, Python picks the natural type of the value. An integer prints as a whole number, a string prints as text, and a float prints in its short form. The type letter forces a specific form, which is useful when you want every row of a table to look the same.

Worked example 1: the common widths, alignments and precisions

The first program prints one value per line in several shapes. Read each line and predict the output before you look below the code.

Python · editable

The output is:

42
[   42]
[42   ]
[00042]
3.14
[     3.142]
[Ada       ]
[       Ada]
1,234,567
1,234,567.89
ff FF 10 101
+5 -5
1.234568e+04
1.235e+04
-0003.50
second then first
abc
Tea-----|  2.50|

Here is what each part does.

The width is the minimum number of characters the value takes up. {42:5} pads 42 to 5 characters. Numbers align to the right by default, and text aligns to the left. The alignment < forces left alignment, so {42:<5} gives 42 . The 0 in front of the width pads a number with zeros, so {42:05} gives 00042. For numbers, the 0 pads with zeros on the left. For text it works differently, as the third practice exercise shows.

The precision on a decimal number is the count of digits after the point. .2f gives two digits, so 3.14159 becomes 3.14. For text, precision works in the other direction: .3 keeps at most three characters, so abcdef becomes abc. A text value longer than its width is never cut. {'Ada Lovelace':<3} prints the whole name, because the width is a minimum.

The , option adds thousands separators. Python always uses a comma and a point in an f-string, so the output is the same on every computer. That is a difference from some other languages, where the separator follows the country setting.

The positional form {1} then {0} with str.format lets you reuse the arguments in a different order. The number inside the braces is the position of the value. This is useful for translated messages, where the word order differs between languages.

The fill character comes before the alignment. In {'Tea':-<8}, the fill is -, the alignment is < and the width is 8, so the text Tea is followed by five dashes. The fill can be any character, including a space.

Worked example 2: a price table with a width computed at run time

Sometimes you do not know the width before the program runs. The longest item name decides how wide the first column must be. The program below measures the longest name first, then builds the table with that width. The width can be put inside a spec with a second pair of braces, as in {name:<{width}}.

Python · editable

The output is:

Item           Qty     Price
----------------------------
Apple            3      0.50
Banana bread    12      2.25
Milk             1      2.00
Total: 30.50
3 items

The header and each row use the same width for the first column, so the columns line up no matter which name is longest. The inner braces {name_width} are replaced by the value of the variable before the outer spec is used. That is why the table can change its width without any change to the spec text.

Look at the price for Milk. The stored value is 1.999, and the table shows 2.00. The .2f spec rounds the number to two decimal places when it prints it. The stored value does not change. The total is computed from the stored values, so it is 30.499 in full precision, and it prints as 30.50.

The line with "-" * (...) is a separator. It is the length of the table, computed from the same numbers as the widths. If you change a width in one place, change the separator too, or compute the separator from the same variable.

The last line uses str.format on a template. The empty braces {} take the next value, so the line prints 3 items. The same result comes from an f-string, f"{len(items)} items". Both forms are correct, and f-strings are the shorter choice.

Worked example 3: the older % operator and print

Python code written before f-strings used the % operator. It takes a template with % placeholders and a tuple of values, the same idea as printf in C. You will meet it in older code, so you should be able to read it.

Python · editable

The output is:

Tea     |  2.50|
   42|42   |00042
2 4
010.0

The placeholder %-8s is a text value, left-aligned in 8 characters. The placeholder %6.2f is a decimal number, right-aligned in 6 characters, with two digits after the point. The placeholder %5d is a whole number in 5 characters. The flags work as they do in f-strings: - aligns left and 0 pads with zeros.

The % operator is still supported, but f-strings and str.format are clearer, and they can use the same spec for a value and for its width. Use f-strings in new code.

The print function is the usual way to write a line. It separates its values with a space and ends the line with a newline. Its two options change that. sep sets the separator between values, and end sets the text after the last value. Set end="" to print without a newline, which is useful when a row is built from several parts:

Python · editable

The output is:

Tea       2.50|
Coffee    3.75|
a, b, c

Each row is built from two print calls, and the second one ends the line with | and a newline. Because the first call uses end="", the two parts stay on one line.

Rounding: the same number, different output

Rounding is where beginners are most often surprised, so it deserves its own table. The values below are printed with one decimal place with an f-string.

Python · editable

The output is:

0.25 -> 0.2
0.35 -> 0.3
2.5 -> 2.5

The rule is that Python rounds the value stored in memory, not the decimal digits you typed. The number 0.25 is exactly one quarter, which is a tie between 0.2 and 0.3. Python rounds a tie to the even digit, so 0.25 becomes 0.2. The number 0.35 is stored as a value slightly below 0.35, so it rounds down to 0.3. Neither answer is a bug. Both follow from how binary floats are stored.

The same rule applies to whole numbers. f"{2.5:.0f}" gives 2, and f"{3.5:.0f}" gives 4, because both are ties that go to the even digit. A report that must round halves up needs a different method, such as the decimal module with an explicit rounding mode. For most output, the default is fine, and the lesson is to test the rounding you need with real values before you rely on it in a report.

A second case shows that stored values are not exact. f"{0.1 + 0.2:.20f}" prints 0.30000000000000004441. The sum is not exactly 0.3, and printing more digits shows the difference. Rounding to two places hides it, which is why money is often stored as whole cents rather than as floats.

Common mistakes

Mistake 1: a type letter that does not match the value. The spec d needs a whole number. Passing a decimal number raises an error:

Python · editable

The output is:

ValueError: Unknown format code 'd' for object of type 'float'

The message names the code (d) and the type that was passed (float). The fix is to use f or to pass a whole number. The old % operator behaves differently and does not raise an error: "%d" % 3.5 gives 3, because %d truncates the decimal part. Use the type that matches your data, and do not rely on truncation to hide a bug.

Mistake 2: fewer values than placeholders. If the template has two placeholders and you pass one value, Python stops with an error. Both forms are shown here:

Python · editable

The output is:

IndexError: Replacement index 1 out of range for positional args tuple
TypeError: not enough arguments for format string

Count the placeholders and the values before you run the code. The first message names the position that had no value.

Mistake 3: an unknown type letter. A typo such as q is not a type. Python reports it when it runs the code:

Python · editable

The output is:

ValueError: Unknown format code 'q' for object of type 'str'

Mistake 4: expecting the width to be a limit. The width is the minimum, not the maximum. The text Ada Lovelace does not shrink to fit a width of 3, and the output keeps the whole name. To cut text to a length, use the precision for text, as in .3, which keeps abc.

Mistake 5: a format string built from user input. A format spec is code in a small language. If a user types {0.__class__} into a form and you pass that text to str.format as the template, the program prints something you did not intend, and a crafted template can read attributes of objects. Build the template in your code, and put the user's text in a value.

Mistake 6: forgetting that a float shows its stored value. A value such as 1.999 shows as 2.00 with two decimal places, and a total built from such values can differ from the sum of the printed numbers. Compute with the full values, and round only in the output.

Practice

Try each exercise before you open its answer. Each answer shows the output so you can check your work.

  1. Write an f-string that prints the name Tea left-aligned in 12 columns, followed by the price 2.5 right-aligned in 8 columns with two decimal places.

Show the answer

print(f"{'Tea':<12}{2.5:>8.2f}")

The output is Tea 2.50. The name takes 12 columns and the price takes 8, so the line is 20 characters long.

  1. What does f"{0.4567 * 100:.1f}%" print? Explain each step.

Show the answer

It prints 45.7%. The multiplication gives 45.67, and .1f rounds it to one decimal place, which is 45.7. The % in the text after the spec is an ordinary character, because it is outside the braces. In the % operator, a single percent sign would be read as a placeholder, so you would write %% there.

  1. Predict the output of print(f"{5:04}") and of print(f"{'5':04}"). Then run both and explain the difference.

Show the answer

The first line prints 0005, because the 0 before the width pads a number with zeros on the left. The second line prints 5000, because the value is a string. For text, the 0 acts as a fill character with left alignment, so the padding goes on the right. The text 5 is followed by three zeros.

  1. Write a function money(cents) that prints an integer number of cents as dollars with a comma for thousands and two decimal places, such as 1,234.50 for 123450.

Show the answer

def money(cents):
    return f"{cents / 100:,.2f}"
 
print(money(123450))

The output is 1,234.50. The division by 100 gives a float, and the spec ,.2f adds the thousands separator and two decimal places.

  1. Print the value 1234 as a whole number with eight digits, padded with leading zeros.

Show the answer

print(f"{1234:08d}")

The output is 00001234. The 0 before the width fills with zeros, and 8 is the width. The type d is optional for an integer, so f"{1234:08}" gives the same result.

Summary

  • A format spec goes after a colon inside braces, and it sets the fill, alignment, sign, width, precision and type of a value.
  • The width is a minimum. Use < or > to set the alignment, and a 0 before the width to pad numbers with zeros. A width never cuts a value short.
  • The precision controls decimal places for numbers and the maximum length for text.
  • Python rounds the stored value, so a tie such as 0.25 goes to the even digit. Test the rounding you need before you depend on it.
  • A type letter that does not match the value raises an error in f-strings and str.format. The old %d truncates a decimal instead of raising an error.
  • Put the width or precision in a variable with a nested pair of braces, and use print with end when a row is built from several parts.
Check yourself: Formatting (6 questions) →Practice this topic: coding problems for Strings →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Longest common prefix in Python