Program Arcade Games
With Python And Pygame

Chapter 20: Formatting

Here is a quick table for reference when doing text formatting. For a detailed explanation of how text formatting works, keep reading.

Number Format Output Description
3.1415926 {:.2f} 3.14 2 decimal places
3.1415926 {:+.2f} +3.14 2 decimal places with sign
-1 {:+.2f} -1.00 2 decimal places with sign
3.1415926 {:.0f} 3 No decimal places (will round)
5 {:0>2d} 05 Pad with zeros on the left
1000000 {:,} 1,000,000 Number format with comma separator
0.25 {:.2%} 25.00% Format percentage
1000000000 {:.2e} 1.00e+09 Exponent notation
11 {:>10d}         11 Right aligned
11 {:<10d} 11 Left aligned
11 {:^10d}     11 Center aligned

20.1 Decimal Numbers

Try running the following program, which prints out several random numbers.

import random

for i in range(10):
    x = random.randrange(20)
    print(x)

The output is left justified and numbers look terrible:

16
13
2
0
10
3
18
1
14
5

We can use string formatting to make the list of numbers look better by right-justifying them. The first step is to use the format command on the string. See below:

import random

for i in range(10):
    x = random.randrange(20)
    print("{}".format(x) )

This gets our program closer to right-justifying the number, but we aren't quite there yet. See how the string ends with .format(x). All strings are actually an instances of a class named String. That class has methods that can be called. One of them is format.

The format function will not print out the curly braces {} but instead replaces them with the value in x. The output (below) looks just like what we had before.

7
15
4
12
3
8
7
15
12
8

To right justify, we add more information about how to format the number between the curly braces {}:

import random

for i in range(10):
    x = random.randrange(20)
    print("{:2}".format(x) )

The output:

15
 7
 4
12
 3
 8
 7
15
12
 8

This is better; we have right justified numbers! But how does it work? The :2 that we added isn't exactly intuitive.

Here's the breakdown: The { } tells the computer we are going to format a number. After the : inside the curly braces will be formatting information. In this case we give it a 2 to specify a field width of two characters. The field width value tells the computer to try to fit the number into a field two characters wide. By default, it will try to right-justify numbers and left-justify text.

Even better, the program no longer needs to call str( ) to convert the number to a string. Leave the string conversions out.

What if you had large numbers? Let's make bigger random numbers:

import random

for i in range(10):
    x = random.randrange(100000)
    print("{:6}".format(x) )

This gives output that is right justified, but still doesn't look good:

 18394
 72242
 97508
 21583
 11508
 76064
 88756
 77413
  7930
 81095
 

Where are the commas? This list would look better with separators between each three digits. Take a look at the next example to see how they are added in:

import random

for i in range(10):
    x = random.randrange(100000)
    print("{:6,}".format(x) )

The output:

65,732
30,248
13,802
17,177
 3,584
 7,598
21,672
82,900
72,838
48,557

We added a comma after the field width specifier, and now our numbers have commas. That comma must go after the field width specifier, not before. Commas are included in calculating the field width. For example, 1,024 has a field width of 5, not 4.

We can print multiple values, and combine the values with text. Run the code below.

x = 5
y = 66
z = 777
print("A - '{}' B - '{}' C - '{}'".format(x, y, z))

The program will substitute numbers in for the curly braces, and still print out all of the other text in the string:

A - '5' B - '66' C - '777'

If there are three sets of curly braces, the computer will expect three values to be listed in the format command. The first value given will replace the first curly brace.

Sometimes we may want to print the same value twice. Or show them in a different order than how they were fed into the format function.

x = 5
y = 66
z = 777
print("C - '{2}' A - '{0}' B - '{1}' C again - '{2}'".format(x, y, z))

See that by placing a number in the curly braces, we can specify which parameter passed into the format function we want printed out. Parameters are numbered starting at 0, so x is considered parameter 0.

We can still specify formatting information after a colon. For example:

x = 5
y = 66
z = 777
print("C - '{2:4}' A - '{0:4}' B - '{1:4}' C again - '{2:4}'".format(x, y, z))

We can see that the code above will show the values right justified with a field width of four:

C - ' 777' A - '   5' B - '  66' C again - ' 777'

20.2 Strings

Let's look at how to format strings.

The following list looks terrible.

my_fruit = ["Apples","Oranges","Grapes","Pears"]
my_calories = [4, 300, 70, 30]

for i in range(4):
    print(my_fruit[i], "are", my_calories[i], "calories.")

The output:

Apples are 4 calories.
Oranges are 300 calories.
Grapes are 70 calories.
Pears are 30 calories.

Now try it using the format command. Note how we can put additional text and more than one value into the same line.

my_fruit = ["Apples", "Oranges", "Grapes", "Pears"]
my_calories = [4, 300, 70, 30]

for i in range(4):
    print("{:7} are {:3} calories.".format(my_fruit[i],my_calories[i]) )

The output:

Apples  are   4 calories.
Oranges are 300 calories.
Grapes  are  70 calories.
Pears   are  30 calories.

That's pretty cool, and it looks the way we want it. But what if we didn't want the numbers right justified, and the text left justified? We can use < and > characters like the following example:

my_fruit = ["Apples", "Oranges", "Grapes", "Pears"]
my_calories = [4, 300, 70, 30]

for i in range(4):
    print("{:>7} are {:<3} calories.".format(my_fruit[i],my_calories[i]) )

The output:

 Apples are 4   calories.
Oranges are 300 calories.
 Grapes are 70  calories.
  Pears are 30  calories.

20.3 Leading Zeros

This produces output that isn't right:

for hours in range(1,13):
    for minutes in range(0,60):
        print("Time {}:{}".format(hours, minutes))

The not-very-good output:

Time 8:56
Time 8:57
Time 8:58
Time 8:59
Time 9:0
Time 9:1
Time 9:2

We need to use leading zeros for displaying numbers in clocks. Rather than specify a 2 for the field width, instead use 02. This will pad the field with zeros rather than spaces.

for hours in range(1,13):
    for minutes in range(0,60):
        print("Time {:02}:{:02}".format(hours, minutes))

The output:

Time 08:56
Time 08:57
Time 08:58
Time 08:59
Time 09:00
Time 09:01
Time 09:02

20.4 Floating Point Numbers

We can also control floating point output. Examine the following code and its output:

x = 0.1
y = 123.456789
print("{:.1}  {:.1}".format(x,y) )
print("{:.2}  {:.2}".format(x,y) )
print("{:.3}  {:.3}".format(x,y) )
print("{:.4}  {:.4}".format(x,y) )
print("{:.5}  {:.5}".format(x,y) )
print("{:.6}  {:.6}".format(x,y) )
print()
print("{:.1f}  {:.1f}".format(x,y) )
print("{:.2f}  {:.2f}".format(x,y) )
print("{:.3f}  {:.3f}".format(x,y) )
print("{:.4f}  {:.4f}".format(x,y) )
print("{:.5f}  {:.5f}".format(x,y) )
print("{:.6f}  {:.6f}".format(x,y) )

And here's the output for that code:

0.1  1e+02
0.1  1.2e+02
0.1  1.23e+02
0.1  123.5
0.1  123.46
0.1  123.457

0.1  123.5
0.10  123.46
0.100  123.457
0.1000  123.4568
0.10000  123.45679
0.100000  123.456789

A format of .2 means to display the number with two digits of precision. Unfortunately this means if we display the number 123 which has three significant numbers rather than rounding it we get the number in scientific notation: 1.2e+02.

A format of .2f (note the f) means to display the number with two digits after the decimal point. So the number 1 would display as 1.00 and the number 1.5555 would display as 1.56.

A program can also specify a field width character:

x = 0.1
y = 123.456789
print("'{:10.1}'  '{:10.1}'".format(x,y) )
print("'{:10.2}'  '{:10.2}'".format(x,y) )
print("'{:10.3}'  '{:10.3}'".format(x,y) )
print("'{:10.4}'  '{:10.4}'".format(x,y) )
print("'{:10.5}'  '{:10.5}'".format(x,y) )
print("'{:10.6}'  '{:10.6}'".format(x,y) )
print()
print("'{:10.1f}'  '{:10.1f}'".format(x,y) )
print("'{:10.2f}'  '{:10.2f}'".format(x,y) )
print("'{:10.3f}'  '{:10.3f}'".format(x,y) )
print("'{:10.4f}'  '{:10.4f}'".format(x,y) )
print("'{:10.5f}'  '{:10.5f}'".format(x,y) )
print("'{:10.6f}'  '{:10.6f}'".format(x,y) )

The format 10.2f does not mean 10 digits before the decimal and two after. It means a total field width of 10. So there will be 7 digits before the decimal, the decimal which counts as one more, and 2 digits after.

'       0.1'  '     1e+02'
'       0.1'  '   1.2e+02'
'       0.1'  '  1.23e+02'
'       0.1'  '     123.5'
'       0.1'  '    123.46'
'       0.1'  '   123.457'

'       0.1'  '     123.5'
'      0.10'  '    123.46'
'     0.100'  '   123.457'
'    0.1000'  '  123.4568'
'   0.10000'  ' 123.45679'
'  0.100000'  '123.456789'

20.5 Printing Dollars and Cents

If you want to print a floating point number for cost, you use an f. See below:

cost1  = 3.07
tax1   = cost1 * 0.06
total1 = cost1 + tax1

print("Cost:  ${0:5.2f}".format(cost1) )
print("Tax:    {0:5.2f}".format(tax1) )
print("------------")
print("Total: ${0:5.2f}".format(total1) )

Remember! It would be easy to think that %5.2f would mean five digits, a decimal, followed by two digits. But it does not. It means a total field width of five, including the decimal and the two digits after. Here's the output:

Cost:  $ 3.07
Tax:     0.18
------------
Total: $ 3.25

Danger! The above code has a mistake that is very common when working with financial transactions. Can you spot it? Try spotting it with the expanded code example below:

cost1  = 3.07
tax1   = cost1 * 0.06
total1 = cost1 + tax1

print("Cost:  ${0:5.2f}".format(cost1) )
print("Tax:    {0:5.2f}".format(tax1) )
print("------------")
print("Total: ${0:5.2f}".format(total1) )

cost2  = 5.07
tax2   = cost2 * 0.06
total2 = cost2 + tax2

print()
print("Cost:  ${0:5.2f}".format(cost2) )
print("Tax:    {0:5.2f}".format(tax2) )
print("------------")
print("Total: ${0:5.2f}".format(total2) )


print()
grand_total = total1 + total2
print("Grand total: ${0:5.2f}".format(grand_total) )

Here's the output:

Cost:  $ 3.07
Tax:     0.18
------------
Total: $ 3.25

Cost:  $ 5.07
Tax:     0.30
------------
Total: $ 5.37

Grand total: $ 8.63

Spot the mistake? You have to watch out for rounding errors! Look at that example, it seems like the total should be $ 8.62 but it isn't.

Print formatting doesn't change the number, only what is output! If we changed the print formatting to include three digits after the decimal the reason for the error becomes more apparent:

Cost:  $3.070
Tax:    0.184
------------
Total: $3.254

Cost:  $5.070
Tax:    0.304
------------
Total: $5.374

Grand total: $8.628

Again, formatting for the display does not change the number. Use the round command to change the value and truly round. See below:

cost1 = 3.07
tax1 = round(cost1 * 0.06, 2)
total1 = cost1 + tax1

print("Cost:  ${0:5.2f}".format(cost1) )
print("Tax:    {0:5.2f}".format(tax1) )
print("------------")
print("Total: ${0:5.2f}".format(total1) )

cost2 = 5.07
tax2 = round(cost2 * 0.06,2)
total2 = cost2 + tax2

print()
print("Cost:  ${0:5.2f}".format(cost2) )
print("Tax:    {0:5.2f}".format(tax2) )
print("------------")
print("Total: ${0:5.2f}".format(total2) )


print()
grand_total = total1 + total2
print("Grand total: ${0:5.2f}".format(grand_total) )

Output:

Cost:  $ 3.07
Tax:     0.18
------------
Total: $ 3.25

Cost:  $ 5.07
Tax:     0.30
------------
Total: $ 5.37

Grand total: $ 8.62

The round command controls how many digits after the decimal we round to. It returns the rounded value but does not change the original value. See below:

x = 1234.5678
print(round(x, 2))
print(round(x, 1))
print(round(x, 0))
print(round(x, -1))
print(round(x, -2))

See below to figure out how feeding the round() function values like -2 for the digits after the decimal affects the output:

1234.57
1234.6
1235.0
1230.0
1200.0

20.6 Use in Pygame

We don't just have to format strings for print statements. The example timer.py uses string formatting and blit's the resulting text to the screen to make an on-screen timer:

# Use python string formatting to format in leading zeros
output_string = "Time: {0:02}:{1:02}".format(minutes,seconds)

# Blit to the screen
text = font.render(output_string, True, BLACK)
screen.blit(text, [250, 250])

20.7 Review

20.7.1 Short Answer Worksheet

Click here for the chapter worksheet.


You are not logged in. Log in here and track your progress.