Formatting and printing output

Published

2023-07-31

Some ways to format output

Say we want to write a program which prompts a user for some number and calculates the square of that number. Here’s a program that does just that:

"""A program to square a number provided by the user 
and display the result. """

x = input("Enter a number: ")
x = float(x)
result = x * x
print(result)

That’s fine, but perhaps we could make this more friendly. Let’s say we wanted to print the result like this.

17.0 squared is 289.0

How would we go about it? There are a few ways. One somewhat clunky approach would be to use string concatenation. Now, we cannot do this:

"""A program to square a number provided by the user 
and display the result. """

x = input("Enter a number: ")
x = float(x)
result = x * x
print(x + ' squared is ' + result)

This program fails, with the error:

Traceback (most recent call last):
  File ".../squared_with_concatenation.py", line 4, in <module>
    print(x + ' squared is ' + result)
TypeError: unsupported operand type(s) for +: 'float' and 'str'

Why? Because we cannot concatenate floats and strings. One way to fix this would be to explicitly convert the floating-point values to strings. We can do this—explicitly—by using Python’s built-in function str().

"""A program to square a number provided by the user 
and display the result. """

x = input("Enter a number: ")
x = float(x)
result = x * x
print(str(x) + ' squared is ' + str(result))

Now, this works. Here’s a trial.

Enter a number: 17
17.0 squared is 289.0

Again, this works, but it’s not a particularly elegant solution. If you were to think “there must be a better way” you’d be right!

Python f-strings and string interpolation

The approach described above is valid Python, but there’s a better way. Earlier versions of Python offered a form of string interpolation borrowed from the C programming language and the string format() function (neither of which are presented here). These are still available, but are largely superseded. With Python 3.6 came f-strings.1

f-strings provide an improved approach to string interpolation. They are, essentially, template strings with placeholders for values we wish to interpolate into the string.

An f-string is prefixed with the letter f, thus:

f'I am an f-string, albeit a boring one.'

The prefix tells the Python interpreter that this is an f-string.

Within the f-string, we can include names of objects or expressions we wish to interpolate within curly braces (these are called replacement fields). For example,

>>> name = 'Carol'
>>> f'My name is {name}.'
My name is Carol.
>>> favorite_number = 498
>>> f'My favorite number is {favorite_number}.'
My favorite number is 498.
>>> f'My favorite number is {400 + 90 + 8}.'
My favorite number is 498.

Here’s how we’d solve our earlier problem above using f-strings.

"""A program to square a number provided by the user 
and display the result. """

x = input("Enter a number: ")
x = float(x)
result = x * x
print(f'{x} squared is {result}')

Format specifiers

Let’s say the result of some calculation was 1/3. The decimal expansion of this is 0.333… Let’s print this.

>>> 1 / 3
0.3333333333333333

Now, in this example, it’s unlikely that you’d need or want to display 0.3333333333333333 to quite so many decimal places. Usually, it suffices to print fewer digits to the right of the decimal point, for example, 0.33 or 0.3333. If we were to interpolate this value within an f-string, we’d get a similar result.

>>> x = 1 / 3
>>> f'{x}'
'0.3333333333333333'

Fortunately, we can tell Python how many decimal places we wish to use. We can include a format specifier within our f-string.

>>> x = 1 / 3
>>> f'{x:.2f}'
'0.33'

The syntax for this is to follow the interpolated element with a colon : and then a specifier for the desired format. In the example above, we used .2f meaning “display as a floating-point number to two decimal places precision”. Notice what happens here:

>>> x = 2 / 3
>>> f'{x:.2f}'
'0.67'

Python has taken care of rounding for us, rounding the value 0.6666666666666666 to two decimal places. Unless you have very specific reasons not to do so, you should use f-strings for formatting output.

There are many format specifiers we can use. Here are a few:

Format a floating-point number as a percentage.

>>> x = 2 / 3
>>> f'{x:.2%}'
'66.67%'

Format an integer with comma-separated thousands

>>> x = 1234567890
>>> f'{x:,}'
'1,234,567,890'

Format a floating-point number with comma-separated thousands

>>> gdp = 22996100000000  # USA gross domestic product
>>> population = 331893745  # USA population, 2021 est.
>>> gdp_per_capita = gdp / population
>>> f'${gdp_per_capita:,.2f}'
'$69,287.54'

Scientific notation

Using E as a format specifier will result in normalized scientific notation.

>>> x = 1234567890
>>> f"{x:.4E}"
'1.2346E+09'

Formatting tables

It’s not uncommon that we wish to print data or results of calculations in tabular form. For this, we need to be able to specify width of a field or column, and the alignment of a field or column. For example, say we wanted to print a table like this:2

                      GDP    Population       GDP per
Country       ($ billion)     (million)    capita ($)
-----------------------------------------------------
Chad               11.780        16.818           700
Chile             317.059        19.768        16,039
China          17,734.063     1,412.600        12,554
Colombia          314.323        51.049         6,157

We’d want to left-align the “Country” column, and right-align numbers (numbers, in almost all cases should be right-aligned, and if the decimal point is displayed, all decimal points should be aligned vertically). Let’s see how to do that with f-strings and format specifiers. We’ll start with the column headings.

print(f'{"":<12}'
      f'{"GDP":>16}'
      f'{"Population":>16}'
      f'{"GDP per":>16}')
print(f'{"Country":<12}'
      f'{"($ billion)":>16}'
      f'{"(million)":>16}'
      f'{"capita ($)":>16}')

This would have been a little long as two single lines, so it’s been split into multiple lines.3 This prints the column headings:

                     GDP    Population         GDP per 
Country      ($ billion)     (million)      capita ($)

< is used for left-alignment (it points left). > is used for right-alignment (it points right). The numbers in the format specifiers (above) designate the width of the field (or column), so the country column is 12 characters wide, GDP column is 16 characters wide, etc.

Now let’s see about a horizontal rule, dividing the column headings from the data. For that we can use repeated concatenation.

print('-' * 60)  # prints 60 hyphens

So now we have

print(f'{"":<12}'
      f'{"GDP":>16}'
      f'{"Population":>16}'
      f'{"GDP per":>16}')
print(f'{"Country":<12}'
      f'{"($ billion)":>16}'
      f'{"(million)":>16}'
      f'{"capita ($)":>16}')
print('-' * 60)

which prints:

                      GDP   Population        GDP per
Country       ($ billion)    (million)     capita ($)
-----------------------------------------------------

Now we need to handle the data. Let’s say we have the data in this form:

gdp_chad = 11.780
gdp_chile = 317.059
gdp_china = 17734.063
gdp_colombia = 314.323
pop_chad = 16.818
pop_chile = 19.768
pop_china = 1412.600
pop_colombia = 51.049

(Yeah. This is a little clunky. We’ll learn better ways to handle data later.) We could print the rows in our table like this:

print(f'{"Chad":<12}'
      f'{gdp_chad:>16,.3f}'
      f'{pop_chad:>16,.3f}'
      f'{gdp_chad / pop_chad * 1000:>16,.0f}')

print(f'{"Chile":<12}'
      f'{gdp_chile:>16,.3f}'
      f'{pop_chile:>16,.3f}'
      f'{gdp_chile / pop_chile * 1000:>16,.0f}')

print(f'{"China":<12}'
      f'{gdp_china:>16,.3f}'
      f'{pop_china:>16,.3f}'
      f'{gdp_china / pop_china * 1000:>16,.0f}')

print(f'{"Colombia":<12}'
      f'{gdp_colombia:>16,.3f}'
      f'{pop_colombia:>16,.3f}'
      f'{gdp_colombia / pop_colombia * 1000:>16,.0f}')

(Yeah. This is a little clunky too. We’ll see better ways soon.) Notice that we can combine format specifiers, so for values in the GDP column we have a format specifier

>16,.3f

The first symbol > indicates that the column should be right-aligned. The 16 indicates the width of the column. The , indicates that we should use a comma as a thousands separator. .3f indicates formatting as a floating point number, with three decimal places of precision. Other format specifiers are similar.

Putting it all together we have:

gdp_chad = 11.780
gdp_chile = 317.059
gdp_china = 17734.063
gdp_colombia = 314.323
pop_chad = 16.818
pop_chile = 19.768
pop_china = 1412.600
pop_colombia = 51.049

print(f'{"":<12}'
      f'{"GDP":>16}'
      f'{"Population":>16}'
      f'{"GDP per":>16}')
print(f'{"Country":<12}'
      f'{"($ billion)":>16}'
      f'{"(million)":>16}'
      f'{"capita ($)":>16}')
print('-' * 60)

print(f'{"Chad":<12}'
      f'{gdp_chad:>16,.3f}'
      f'{pop_chad:>16,.3f}'
      f'{gdp_chad / pop_chad * 1000:>16,.0f}')
      
print(f'{"Chile":<12}'
      f'{gdp_chile:>16,.3f}'
      f'{pop_chile:>16,.3f}'
      f'{gdp_chile / pop_chile * 1000:>16,.0f}')

print(f'{"China":<12}'
      f'{gdp_china:>16,.3f}'
      f'{pop_china:>16,.3f}'
      f'{gdp_china / pop_china * 1000:>16,.0f}')

print(f'{"Colombia":<12}'
      f'{gdp_colombia:>16,.3f}'
      f'{pop_colombia:>16,.3f}'
      f'{gdp_colombia / pop_colombia * 1000:>16,.0f}')

which prints:

                      GDP    Population       GDP per
Country       ($ billion)     (million)    capita ($)
-----------------------------------------------------
Chad               11.780        16.818           700
Chile             317.059        19.768        16,039
China          17,734.063     1,412.600        12,554
Colombia          314.323        51.049         6,157

Original author: Clayton Cafiero < [given name] DOT [surname] AT uvm DOT edu >

No generative AI was used in producing this material. This was written the old-fashioned way.

This material is for free use under either the GNU Free Documentation License or the Creative Commons Attribution-ShareAlike 3.0 United States License (take your pick).

Footnotes

  1. “F-string” is short for “formatted string literal”. These were introduced in Python 3.6. For details, see the Input and Output section of the official Python tutorial (https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals), and PEP 498 (https://peps.python.org/pep-0498/) for a complete rationale behind literal string interpolation.↩︎

  2. Sources: 2021 GDP from World Bank (https://data.worldbank.org/); 2021 population from the United Nations (https://www.un.org/development/desa/pd/).↩︎

  3. This takes advantage of the fact that when Python sees two strings without an operator between them it will concatenate them automatically. Don’t do this just to save keystrokes. It’s best to reserve this feature for handling long lines or building long strings across multiple lines.↩︎