Program Arcade Games
With Python And PygameChapter 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.
English version by Paul Vincent Craven
Spanish version by Antonio Rodríguez Verdugo
Russian version by Vladimir Slav
Turkish version by Güray Yildirim
Portuguese version by Armando Marques Sobrinho and Tati Carvalho
Dutch version by Frank Waegeman
Hungarian version by Nagy Attila
Finnish version by Jouko Järvenpää
French version by Franco Rossi
Korean version by Kim Zeung-Il
Chinese version by Kai Lin