↓ Skip to main content

String formatting in Python

·1601 words·8 mins·
 Author
Author
Francisco Bustamante
Chemist, data scientist and Python programmer.
Table of Contents

We spend a considerable amount of time dealing with strings, so it is important to know how to present them the way we want. That is what we will see in this article, with plenty of examples.

Placeholder
#

This is the oldest way of formatting strings in the language. Nowadays, it is recommended to use the other forms presented later in the article. However, since there is still a lot of legacy code out there, it is important to know how to use it.

As the name suggests, it uses placeholders, such as %s, inside the string. For example:

print('A text will be placed %s' % 'here')
A text will be placed here

When more than one insertion is needed, a tuple is passed with each insertion in the order it must appear in the string:

print('A text will be placed %s, and %s too' % ('here', 'more here'))
A text will be placed here, and more here too

Sometimes we need to pass a string that has a special meaning for the language interpreter, but we want the interpreter to treat it as a normal string. We call these cases raw strings, literally “uncooked” strings. For example, \t in Python represents the space of the TAB key. If we want \t to be displayed literally instead of the space, we indicate it with %r, where the r stands for raw string:

print('Here is the TAB space: \t. Now the raw representation: %r.' % '\t')
Here is the TAB space: 	. Now the raw representation: '\t'.

Dealing with numbers
#

If you want a faithful representation of the number passed, use %s so that it is converted to a string. But manipulations can be done, such as truncating to an integer and showing the sign:

num = 13.744

print('Numbers like %s are converted to string' % num)

print('They can also be represented as integers: %d; note there is no rounding' % num)

print('Showing the sign in integers: %+d' % num)
print('Showing the sign in floats: %+f' % num)
Numbers like 13.744 are converted to string

They can also be represented as integers: 13; note there is no rounding

Showing the sign in integers: +13
Showing the sign in floats: +13.744000

Note above that the float representation has more decimal places than the number passed. We can change this representation with x.yf, where x is the minimum number of characters and y, the decimal places:

print('Minimum of 5 characters with 2 decimal places: %5.2f' % num)
print('Minimum of 1 characters with 0 decimal places: %1.0f (note the rounding)' % num)
print('Minimum of 1 characters with 5 decimal places: %1.5f' % num)
print('Minimum of 10 characters with 2 decimal places: %10.2f' % num)
print('Minimum of 10 characters with 2 decimal places: %+10.2f (with sign)' % num)
Minimum of 5 characters with 2 decimal places: 13.74
Minimum of 1 characters with 0 decimal places: 14 (note the rounding)
Minimum of 1 characters with 5 decimal places: 13.74400
Minimum of 10 characters with 2 decimal places:      13.74
Minimum of 10 characters with 2 decimal places:     +13.74 (with sign)

The format method
#

This form was introduced in Python 3.0 and is more flexible and more readable than the previous one.

With a single insertion, it looks like this:

print('A text will go in {}.'.format('here'))
A text will go in here.

A great improvement of format is being able to choose the order of insertion and to work with variables:

print('Controlling {2} {1}{0}'.format('!', 'order', 'the'))

print('First: {a}; Second: {b}; Third: {c}'.format(a=1, b='2', c=12.3))

print('The variable {p} can be reused: {p}.'.format(p='(hi!)'))
Controlling the order!
First: 1; Second: 2; Third: 12.3
The variable (hi!) can be reused: (hi!).

We can even use dictionary keys through unpacking, as in the following example:

person = {'name': 'Chico', 'age': 33}

print('Hello, {name}. You are {age} years old.'.format(**person))
Hello, Chico. You are 33 years old.

Alignment
#

With format there was a great advance in the possibilities of displaying text output. Below, we see how to indicate the space to be occupied by each insertion:

print('{0:16} | {1:5}'.format('Name', 'Age'))
print('{0:16} | {1:5}'.format('Chico', 33))
print('{0:16} | {1:5}'.format('Brás Cubas', 64))
print('{0:16} | {1:5}'.format('Machado de Assis', '69'))
Name             | Age  
Chico            |    33
Brás Cubas       |    64
Machado de Assis | 69   

See how the numeric values were aligned to the right, while the strings were aligned to the left. We can control the alignment as follows:

print('{0:<8} | {1:^8} | {2:>8}'.format('Left', 'Center', 'Right'))
print('{0:<8} | {1:^8} | {2:>8}'.format(11, 22, 33))
Left     |  Center  |    Right
11       |    22    |       33

It is also possible to fill the space with some character:

print('{0:=<8} | {1:-^8} | {2:.>8}'.format('Left', 'Center', 'Right'))
print('{0:=<8} | {1:-^8} | {2:.>8}'.format(11, 22, 33))
Left==== | -Center- | ...Right
11====== | ---22--- | ......33

The alignment and size information can also be passed as parameters:

print('"{:{align}{width}}"'.format('text', align='^', width='10'))
"   text   "

Dealing with numbers
#

Very similar to what we saw with placeholders:

print('10 characters and 2 decimal places:{:10.2f}'.format(13.579))
10 characters and 2 decimal places:     13.58

The novelty is the possibility of controlling the distance from the sign to the first digit:

print('{:=5d}'.format(-42))
print('{:=+5d}'.format(42))  # forcing the + to appear on a positive number
-  42
+  42

And here we can also use parameters:

print('10 characters and 2 decimal places:{:{sign}{width}.{prec}f}'.format(13.579, width=10, prec=2, sign='+'))
10 characters and 2 decimal places:    +13.58

Dealing with dates
#

The format method makes it easy to format datetime objects:

from datetime import datetime

print('{:%Y-%m-%d %H:%M}'.format(datetime(1969, 7, 20, 22, 56)))
1969-07-20 22:56

To learn more, see the format documentation.

f-strings
#

Available since Python 3.6, f-strings advanced even further in flexibility and readability. Basic use is very simple: just put f (or F) before the string and the variable between braces:

name = 'Chico'
print(f'Name: {name}.')
Name: Chico.

F-strings are evaluated at runtime, so expressions can be passed directly:

print(f'Name: {name.upper()}')
Name: CHICO

In the case above, we called the string method upper to present the name in uppercase. We can also pass mathematical operations:

print(f'{2 * 3}')
6

This is so simple and handy that I will even throw in a multiplication table in 3 lines of code:

print('Multiplication table of 2:')
for i in range(11):
    print(f'2 x {i:2} = {2 * i:2}')
Multiplication table of 2:
2 x  0 =  0
2 x  1 =  2
2 x  2 =  4
2 x  3 =  6
2 x  4 =  8
2 x  5 = 10
2 x  6 = 12
2 x  7 = 14
2 x  8 = 16
2 x  9 = 18
2 x 10 = 20

:-) See in the code above that it is also possible to pass instructions for string length and alignment. Here, I reserved two spaces for the numbers so that they would be aligned to the right.

Dealing with numbers
#

From personal experience, dealing with numbers in f-strings was a bit difficult at first. That is because what used to be the count of decimal places became the count of the total number of digits. Let us look at the following example, where I present the format method and the f-string:

num = 23.45678

print('10 characters and 4 decimal places:{:10.4f} (format)'.format(num))

print(f'10 characters and 4 decimal places:{num:{10}.{6}} (f-string)')
10 characters and 4 decimal places:   23.4568 (format)
10 characters and 4 decimal places:   23.4568 (f-string)

Note in the code above that the 6 indicates the total number of digits: 2 before the decimal point and 4 decimal places. This is a bit confusing, but we will see a more intuitive way ahead.

When we saw the previous forms of formatting, trailing zeros were added so that the number had the requested number of decimal places. This does not happen here:

num = 23.45

print('10 characters and 4 decimal places:{:10.4f} (format)'.format(num))

print(f'10 characters and 4 decimal places:{num:{10}.{6}} (f-string)')
10 characters and 4 decimal places:   23.4500 (format)
10 characters and 4 decimal places:     23.45 (f-string)

These “oddities” can be easily worked around. Just use the formatting syntax we saw in the format method. It is recognized in f-strings! Like this:

num = 23.45

print('10 characters and 4 decimal places:{:10.4f} (format)'.format(num))

print(f'10 characters and 4 decimal places:{num:10.4f} (f-string)')
10 characters and 4 decimal places:   23.4500 (format)
10 characters and 4 decimal places:   23.4500 (f-string)

Alignment
#

What we saw about alignment in the format section still holds here. And we can also use runtime evaluation. See the example:

people = {
    'chico': {'name': 'Chico', 'age': 33, 'profession': 'Teacher'},
    'bras': {'name': 'Brás Cubas', 'age': 64, 'profession': 'Dead author'},
    'machado': {'name': 'Machado de Assis', 'age': 69, 'profession': 'Writer'},
}

print(f"{'Name':^16} | {'Age':^6} | {'Profession':^15}")
print(f"{'-'*16} | {'-'*6} | {'-'*15}")

for person in people:
    d = people[person]
    print(f"{d['name']:<16} | {d['age']:^6} | {d['profession']:>15}")
      | Name             | Age   | Profession      |
      | ---------------- | ----- | --------------- |
      | Chico            | 33    | Teacher         |
      | Brás Cubas       | 64    | Dead author     |
      | Machado de Assis | 69    | Writer          |

Dealing with dates
#

Just as when using format, it is also easy to format dates with f-strings:

now = datetime.now()

print(f'{now:%Y-%m-%d %H:%M}')
2022-03-24 15:47

To learn more, see the f-strings documentation.

Conclusion
#

We spend a considerable amount of time dealing with strings, so it is important to know how to present them the way we want.

Did you like this article? It is part of the programming Drops, a set of shorter posts focused on fundamentals, covering some aspects of the Python language and of programming in general. You can read more of these articles by browsing the tag “drops” here on the site.

See you next time!

Related