## Python print() statements: part 4 f-strings

This post is long overdue. In 2016, I wrote three posts on how to achieve well formatted print statements (1, 2, 3). However, on December 23, 2016, Python 3.6 was released, which added f-string.

### What are f-strings?

f-strings are a string formatting syntax. They are similar to `.format()`

in that they allow us to generate well formatted strings. However, they are easier to read and write, and usually involve less typing.

### What can f-strings do?

f-strings can do everything `.format()`

can, and more. Let’s start by updating some of the examples included in my previous posts to now use f-string.

Example from the first post:

```
num = 1
denom = 3
val = num/denom
# .format()
print('{} divided by {} equals {}'.format(num, denom, val))
# Revised to use f-strings
print(f'{num} divded by {denom} equals {val}')
```

The output of both print statements is the same:

```
1 divded by 3 equals 0.3333333333333333
```

Examples from the second post:

```
from math import pi
# .format()
print('Pi with 6 total spaces, including 5 digits after the decimal point {:6.5f}'.format(pi))
print('Pi with 2 total spaces, including 1 digits after the decimal point {:2.1f}'.format(pi))
print('Pi with 8 total spaces, including 1 digits after the decimal point {:8.1f}'.format(pi))
# Revised to use f-strings
print(f'Pi with 6 total spaces, including 5 digits after the decimal point {pi:6.5f}')
print(f'Pi with 2 total spaces, including 1 digits after the decimal point {pi:2.1f}')
print(f'Pi with 8 total spaces, including 1 digits after the decimal point {pi:8.1f}')
```

The outputs are the same:

```
Pi with 6 total digits, including 5 digits after the decimal point 3.14159
Pi with 4 total digits, including 1 digits after the decimal point 3.1
Pi with 8 total digits, including 1 digits after the decimal point 3.1
```

Examples from the third post:

```
# .format()
# Example 1
print('L {:<20} R'.format('x'))
# Example 2
print('L {:^20} R'.format('x'))
# Example 3
print('L {:>20} R'.format('x'))
# Revised to use f-strings
# Example 1
print(f'L {"x":<20} R')
# Example 2
print(f'L {"x":^20} R')
# Example 3
print(f'L {"x":>20} R')
```

Same:

```
L x R
L x R
L x R
```

```
# .format()
print ('{:=<20}'.format('hello'))
print ('{:_^20}'.format('hello'))
print ('{:|>20}'.format('hello'))
# Revised to use f-strings
print (f'{"hello":=<20}')
print (f'{"hello":_^20}')
print (f'{"hello":|>20}')
```

Same:

```
hello===============
_______hello________
|||||||||||||||hello
```

```
data = [['NAME', 'AGE', 'HANDEDNESS', 'SCORE (%)'],
['Martin', 38, 'L', 54.123],
['Marty', 33, 'L', 32.438],
['Martine', 25, 'R', 71.128],
['Martyn', 59, 'R', 50.472],
['Mart', 23, 'L', 2.438],
['Martyne', 15, 'R', 71.128],
['Marlyn', 101, 'R', 0.472],
['Marti', 2, 'L', 55.438],
['Mardi', 9, 'R', 81.128],
['Martyne', 49, 'R', 24.472],
['Marteen', 91, 'L', 1.128]]
dash = '-' * 40
# .format()
for i in range(len(data)):
if i == 0:
print(dash)
print('{:<10s}{:>4s}{:>12s}{:>12s}'.format(data[i][0], data[i][1], data[i][2], data[i][3]))
print(dash)
else:
print('{:<10s}{:>4d}{:^12s}{:>12.1f}'.format(data[i][0], data[i][1], data[i][2], data[i][3]))
# Revised to use f-strings
for i in range(len(data)):
if i == 0:
print(dash)
print(f'{data[i][0]:<10s}{data[i][1]:>4s}{data[i][2]:>12s}{data[i][3]:>12s}')
print(dash)
else:
print(f'{data[i][0]:<10s}{data[i][1]:>4d}{data[i][2]:>12s}{data[i][3]:>12.1f}')
```

And, you guessed it, same:

```
----------------------------------------
NAME AGE HANDEDNESS SCORE (%)
----------------------------------------
Martin 38 L 54.1
Marty 33 L 32.4
Martine 25 R 71.1
Martyn 59 R 50.5
Mart 23 L 2.4
Martyne 15 R 71.1
Marlyn 101 R 0.5
Marti 2 L 55.4
Mardi 9 R 81.1
Martyne 49 R 24.5
Marteen 91 L 1.1
```

### What additional things can f-strings do?

f-strings are evaluated at runtime. That means we can use valid Python expressions in them. For example:

```
>>> f'The sum of 5 and 2 is {5+2}'
'The sum of 5 and 2 is 7'
>>> f'2 to the power of 10 is {2**10}'
'2 to the power of 10 is 1024'
>>> f'2 to the power of 8, 10, 12, 26 is {[2**power for power in [8, 10, 12, 16]]}'
'2 to the power of 8, 10, 12, 26 is [256, 1024, 4096, 65536]'
```

You can also call functions within f-strings:

```
def remove_first_e(word):
found_e = False
revised_word = list()
for letter in word:
if not found_e and letter == 'e':
found_e = True
else:
revised_word.append(letter)
return ''.join(revised_word)
name = 'Martin Heroux'
print(f'{name} with the first e removed is {remove_first_e(name)}')
```

The above will print:

```
Martin Heroux with the first e removed is Martin Hroux
```

You can also use methods in f-strings:

```
name = 'marTin HeRoUx'
print(f'We will make {name} into allcaps, which produces {name.upper()}')
```

The above will print:

```
We will make marTin HeRoUx into allcaps, which produces MARTIN HEROUX
```

And, for long print statements, you can use multiple f-strings within the same statement:

```
# This won't work
mean, sd, min, max = 44, 7, 29, 79
print (f'The mean value is {mean}\n'
'The standard deviation is {sd}\n'
'The min is {min}\n'
'The max is {max}\n')
# But this will
print (f'The mean value is {mean}\n'
f'The standard deviation is {sd}\n'
f'The min is {min}\n'
f'The max is {max}\n')
```

The incorrect version generates:

```
The mean value is 44
The standard deviation is {sd}
The min is {min}
The max is {max}
```

Whereas the correct version generates:

```
The mean value is 44
The standard deviation is 7
The min is 29
The max is 79
```

### Conclusion

As you can see, f-strings are super useful. Once you start using them you will never go back!