31. String Formatting¶
Looking for an easy way to format variables for display, and mix them with other text? Add thousands separators (commas in the US) to a large number, and format decimals the way you want them? You need “string formatting.”
Python has three ways to format strings. The old string formatting which originally came from how older languages such as C did formatting. The newer way introduced in Python 3 that was supposed to be better, but arguably wasn’t. And the new way introduced in Python 3.6 with PEP498 . We’ll cover the newest way.
31.1. Quick Reference¶
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 




2 decimal places 



2 decimal places with sign 



2 decimal places with sign 



No decimal places (will round) 



Pad with zeros on the left 



Number format with comma separator 



Format percentage 



Exponent notation 



Right aligned 



Left aligned 



Center aligned 
31.2. Decimal Numbers¶
Try running the following program, which prints out several random numbers.
1 2 3 4 5  import random
for i in range(10):
x = random.randrange(120)
print("My number: ", x)

The output is left justified and numbers look terrible:
My number: 30
My number: 2
My number: 101
My number: 3
My number: 44
My number: 111
My number: 100
My number: 48
My number: 27
My number: 92
We can use string formatting to make the list of numbers look better by rightjustifying them. The first step is to use the “Literal String Interpolation” on the string. See below:
import random
for i in range(10):
x = random.randrange(120)
print(f"My number: {x}")
This gets our program closer to rightjustifying the number, but we aren’t
quite there yet. See how the string starts with f
?
The string will not print out the curly braces {}
but instead replace
them with the value in x
. The output (below) looks just like what we had before.
My number: 23
My number: 92
My number: 102
My number: 19
My number: 85
My number: 114
My number: 37
My number: 101
My number: 35
My number: 18
To right justify, we add more information about how to format the number
to the curly braces {}
:
1 2 3 4 5  import random
for i in range(10):
x = random.randrange(120)
print(f"My number: {x:3}")

My number: 37
My number: 108
My number: 117
My number: 55
My number: 19
My number: 97
My number: 78
My number: 12
My number: 29
My number: 0
This is better; we have right justified numbers! But how does it work?
The :3
that we added isn’t exactly intuitive. Looks like we just
added a random emoji.
Here’s the breakdown: The { }
tells the computer we are going to format a
number. Inside we put the variable we want to format, x
in this case.
After the variable, we put a :
to tell the computer we are about to give it
formatting information.
In this case we give it a 3 to specify a field width of three characters. The field width value tells the computer to try to fit the number into a field three characters wide. By default, it will try to rightjustify numbers and leftjustify 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:
1 2 3 4 5  import random
for i in range(10):
x = random.randrange(100000)
print(f"My number: {x:6}")

This gives output that is right justified, but still doesn’t look good.
My number: 89807
My number: 5177
My number: 24067
My number: 19887
My number: 54155
My number: 49288
My number: 31412
My number: 49633
My number: 43406
My number: 37398
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:
1 2 3 4 5  import random
for i in range(10):
x = random.randrange(100000)
print(f"My number: {x:6,}")

The output:
My number: 86,631
My number: 57,165
My number: 19,835
My number: 22,560
My number: 43,161
My number: 16,604
My number: 20,544
My number: 33,906
My number: 89,846
My number: 27,350
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.
1 2 3 4  x = 5
y = 66
z = 777
print(f"A  '{x}' B  '{y}' C  '{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'
31.3. Strings¶
Let’s look at how to format strings.
The following list looks terrible.
1 2 3 4 5  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.
1 2 3 4 5  my_fruit = ["Apples", "Oranges", "Grapes", "Pears"]
my_calories = [4, 300, 70, 30]
for i in range(4):
print(f"{my_fruit[i]:7} are {my_calories[i]:3} calories.")

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 the
<
and >
characters like the following example:
1 2 3 4 5  my_fruit = ["Apples", "Oranges", "Grapes", "Pears"]
my_calories = [4, 300, 70, 30]
for i in range(4):
print(f"{my_fruit[i]:>7} are {my_calories[i]:<3} calories.")

Apples are 4 calories.
Oranges are 300 calories.
Grapes are 70 calories.
Pears are 30 calories.
31.4. Leading Zeros¶
This produces output that isn’t right:
1 2 3  for hours in range(1,13):
for minutes in range(0,60):
print(f"Time {hours}:{minutes}")

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.
1 2 3  for hours in range(1, 13):
for minutes in range(0, 60):
print(f"Time {hours:02}:{minutes:02}")

Time 08:56
Time 08:57
Time 08:58
Time 08:59
Time 09:00
Time 09:01
Time 09:02
31.5. Floating Point Numbers¶
We can also control floating point output. Examine the following code and its output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  x = 0.1
y = 123.456789
print(f"{x:.1} {y:.1}")
print(f"{x:.2} {y:.2}")
print(f"{x:.3} {y:.3}")
print(f"{x:.4} {y:.4}")
print(f"{x:.5} {y:.5}")
print(f"{x:.6} {y:.6}")
print()
print(f"{x:.1f} {y:.1f}")
print(f"{x:.2f} {y:.2f}")
print(f"{x:.3f} {y:.3f}")
print(f"{x:.4f} {y:.4f}")
print(f"{x:.5f} {y:.5f}")
print(f"{x:.6f} {y:.6f}")

1 2 3 4 5 6 7 8 9 10 11 12 13  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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  x = 0.1
y = 123.456789
print(f"My number: '{x:10.1}' and '{y:10.1}'")
print(f"My number: '{x:10.2}' and '{y:10.2}'")
print(f"My number: '{x:10.3}' and '{y:10.3}'")
print(f"My number: '{x:10.4}' and '{y:10.4}'")
print(f"My number: '{x:10.5}' and '{y:10.5}'")
print(f"My number: '{x:10.6}' and '{y:10.6}'")
print()
print(f"My number: '{x:10.1f}' and '{y:10.1f}'")
print(f"My number: '{x:10.2f}' and '{y:10.2f}'")
print(f"My number: '{x:10.3f}' and '{y:10.3f}'")
print(f"My number: '{x:10.4f}' and '{y:10.4f}'")
print(f"My number: '{x:10.5f}' and '{y:10.5f}'")
print(f"My number: '{x:10.6f}' and '{y:10.6f}'")

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.
My number: ' 0.1' and ' 1e+02'
My number: ' 0.1' and ' 1.2e+02'
My number: ' 0.1' and ' 1.23e+02'
My number: ' 0.1' and ' 123.5'
My number: ' 0.1' and ' 123.46'
My number: ' 0.1' and ' 123.457'
My number: ' 0.1' and ' 123.5'
My number: ' 0.10' and ' 123.46'
My number: ' 0.100' and ' 123.457'
My number: ' 0.1000' and ' 123.4568'
My number: ' 0.10000' and ' 123.45679'
My number: ' 0.100000' and '123.456789'
31.6. Printing Dollars and Cents¶
If you want to print a floating point number for cost, you use an f. See below:
1 2 3 4 5 6 7 8  cost1 = 3.07
tax1 = cost1 * 0.06
total1 = cost1 + tax1
print(f"Cost: ${cost1:5.2f}")
print(f"Tax: {tax1:5.2f}")
print(f"")
print(f"Total: ${total1:5.2f}")

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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  cost1 = 3.07
tax1 = cost1 * 0.06
total1 = cost1 + tax1
print(f"Cost: ${cost1:5.2f}")
print(f"Tax: {tax1:5.2f}")
print(f"")
print(f"Total: ${total1:5.2f}")
cost2 = 5.07
tax2 = cost2 * 0.06
total2 = cost2 + tax2
print()
print(f"Cost: ${cost2:5.2f}")
print(f"Tax: {tax2:5.2f}")
print(f"")
print(f"Total: ${total2:5.2f}")
print()
grand_total = total1 + total2
print(f"Grand total: ${grand_total:5.2f}")

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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  cost1 = 3.07
tax1 = round(cost1 * 0.06, 2)
total1 = cost1 + tax1
print(f"Cost: ${cost1:5.2f}")
print(f"Tax: {tax1:5.2f}")
print(f"")
print(f"Total: ${total1:5.2f}")
cost2 = 5.07
tax2 = round(cost2 * 0.06, 2)
total2 = cost2 + tax2
print()
print(f"Cost: ${cost2:5.2f}")
print(f"Tax: {tax2:5.2f}")
print(f"")
print(f"Total: ${total2:5.2f}")
print()
grand_total = total1 + total2
print(f"Grand total: ${grand_total:5.2f}")

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:
1 2 3 4 5 6  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
31.7. Use in Arcade Programs¶
We don’t just have to format strings for print statements. The example timer.py uses string formatting to make an onscreen timer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24  def on_draw(self):
""" Use this function to draw everything to the screen. """
# Start the render. This must happen before any drawing
# commands. We do NOT need an stop render command.
arcade.start_render()
# Calculate minutes
minutes = int(self.total_time) // 60
# Calculate seconds by using a modulus (remainder)
seconds = int(self.total_time) % 60
# Figure out our output
output = f"Time: {minutes:02d}:{seconds:02d}"
# Output the timer text.
arcade.draw_text(output, 300, 300, arcade.color.BLACK, 30)
def update(self, delta_time):
"""
All the logic to move, and the game logic goes here.
"""
self.total_time += delta_time

You can also use it to display the score, or any other statistics you’d like to show the player.