Welcome to the first Python Tidbit, a series of brief weekly digests summarizing a Python feature or concept.

I will be posting these tidbits every Wednesday at 12:00 pm UTC so stay tuned for those special Wednesday wisdoms! 🚀

The topic of our first tidbit are f-strings!


f-strings, an acronym for format strings, are used to conveniently format and "style" complex string literals.

In their simplest and most common form, they can be used to plug in a variable's value in a string literal.

name = "Jake"
print(f"Hi, my name's {name}!")

Outputs:

Hi, my name's Jake!

... but f-strings are WAY more than just that! Let us see some of the cooler features of f-strings.

Rounding and Precision

A very common practice when dealing with floating point numbers is rounding them, adjusting decimal places, significant figures, etc.

f-strings make that process much simple:

Decimal Places

The f-string in the following code rounds the given number to 2 decimal places.

latency = 30.619325
print(f"Server latency is {latency:.2f} ms")  # Server latency is 30.62

Here, : is used to separate the value being formatted (latency) and the "format specifier" (.2f)

In .2f format specifier:

  • . indicates that we are setting precision

  • 2 is the precision

  • f indicates that the formatted number should be a float.

Significant Digits

Like f is used for decimal places, g format specifier can be used to format a number to given number of significant digits.

latency = 30.619325
print(f"Server latency is {latency:.2g} ms")  # Server latency is 31
print(f"Server latency is {latency:.6g} ms")  # Server latency is 30.6193

Scientific Notation

Another useful format specifier is e which formats the number in scientific notation with given number of digits after decimal.

latency = 30.619325
print(f"Server latency is {latency:.2e} ms")  # Server latency is 3.06e+01

E is another variant of e that uses capital E in resulting string and uppercases inf to INF for very large numbers.

Percentages

The % format specifier shows the given number in percentage format. That is, multiply the given number with 100 and append a % sign.

probability = 0.489
print(f"Chances of rain: {probability:%}")  # Chances of rain: 48.900000%

Of course, a more human output would set a precision as well:

probability = 0.489
print(f"Chances of rain: {probability:.2%}")  # Chances of rain: 48.90%

Grouping

We can easily comma separate large numbers using f-strings.

world_population = 8062000000

print(f"The world poulation right now: {world_population:,} people")

Outputs:

The world population right now: 8,062,000,000 people

You could use _ as separator as well instead of ,.

For floating point numbers, you might want to provide a precision of zero in some cases to strip of the .0 part:

world_population = 8.062e9

print(f"Without precision: {world_population:,}")
print(f"With precision: {world_population:,.0f}")

Gives the output:

Without precision: 8,062,000,000.0
With precision: 8,062,000,000

Aligning Strings

We are not just limited to numbers. We can format strings as well. More specifically, f-strings provide an easy way of aligning strings and setting widths.

Minimum Width

To begin with, we can constrain a string to a minimum width (length).

>>> string = "Hello"
>>> print(f"{string:4}")
'Hello'

The string was returned unchanged because it is already of width of 5 which is greater than provided 4 width.

Now, check this out:

>>> print(f"{string:8}")
'Hello   '

Three spaces were automatically appended at the end of the string to ensure it is of width 8.

Alignment

We can actually control the direction in which spaces are added, or in other words, the string is aligned. For this, we use > (right align), < (left align), or ^ (center align):

>>> print(f"{string:<10}")  # right align (default)
'Hello     '
>>> print(f"{string:>10}")  # left align
'     Hello'
>>> print(f"{string:^10}"  # center align
'  Hello   '

Fill Character

Last but not the least, we can change the character that is used to fill instead of blank space. It is provided (without any quotes) before the alignment specifier.

>>> heading = "Section 1"
>>> print(f"{heading:.^20}")  # center align and fill with dot character
.....Section 1......

An example usage of string formatting is effortlessly crafting complex tables for showcasing information in the terminal. See the following code, for example:

# (name, color, unit price, quantity)
fruits_info = [
    ("Apple", "Red", 1.31, 5),
    ("Banana", "Yellow", 0.98, 8),
    ("Peach", "Orange", 2.40, 3),
    ("Pear", "Green", 6.73, 1),
]

# Print the header of table
header = ("Fruit Name", "Color", "Unit Price", "Quantity", "Total Price")

for item in header:
    print(f"{item:^15}", end="")

print()  # create newline for rows

# Print each row.
for name, color, unit_price, quantity in fruits_info:
    price = unit_price * quantity

    print(f"{name:^15}", end="")
    print(f"{color:^15}", end="")
    print(f"{unit_price:^15.2f}", end="")
    print(f"{quantity:^15}", end="")
    print(f"{price:^15.2f}", end="")
    print()  # create newline for next row

The output of the code above:

Fruit Name        Color       Unit Price      Quantity      Total Price  
     Apple           Red           1.31             5            6.55      
    Banana         Yellow          0.98             8            7.84      
     Peach         Orange          2.40             3            7.20      
     Pear           Green          6.73             1            6.73

... beautiful! 😘👌

Debugging

Before I end, I want to talk about a simple yet so valuable feature when it comes to debugging complex code.

We can print a variable's name alongside its value using a simple syntax of appending = in front of variable name.

This allows for easy inspection of variables:

import datetime
a = 2
b = 3
created_at = datetime.datetime(2024, 3, 28)

print(f"{a=} {b=} {created_at.day=}")

Outputs:

a=2 b=3 created_at.day=28

Order and Type of Format Specifiers

The ordering of format specifiers matters. For example, you cannot provide precision specifier before the alignment and fill character specifiers.

To summarize, the following order is followed for format specifiers:

[fill][alignment][width][grouping][precision]

Furthermore, there are format specifiers that are data type specific. For example, .2f (decimal places) is not supported on non-float types, obviously.

There are other components of f-strings that were not covered in this article and are not included in the order description above.

Conclusion

Of course, there's a lot more to say on f-strings but I don't want to turn this tidbit into an entire book chapter!

Interestingly, there's actually a "mini language" for manipulating formats in Python that defines all the format specifiers we have seen above along with many others.

Read more about it in Python's Documentation!

Got something to say? Feel free to comment on this article and share what you think about f-strings!

I'll see you next Wednesday with another cool Python feature. Ciao! 👋