f'{strings}' are so useful!!!

Unraveling Python's String Formatting Syntax

Python, an incredibly versatile programming language, offers a rich array of features. One of them is the string formatting syntax. This capability enables us to insert various objects, often other strings, directly into our strings.

>>> name = "Andrew"
>>> print(f"My name is {name}. What's your name?")
My name is Andrew. What's your name?

This mechanism doesn't just stop at simple insertions; it also allows the embedding of expressions within these strings.

>>> name = "Andrew"
>>> print(f"{name}, which starts with {name[-1]}")
Andrew, ends with w

Beyond this, Python's string formatting syntax provides tools to control the formatting of each inserted string component, lending your code an even greater degree of flexibility and precision.

At first glance, Python's string formatting syntax may appear quite complex, with a myriad of terms and concepts. Now, let us look at the definitions associated with f strings.

Decoding the Jargon

Before diving into examples, let's first acquaint ourselves with some key terminology used in Python documentation on string formatting. These terms are crucial to understanding and effectively using this feature:

  1. Replacement field: This term refers to any component within an f-string that is enclosed in curly braces ({ and }). It is the place where objects are inserted into the string.
  2. Conversion field: Marked by an exclamation mark (!), this "converts" the object within a replacement field using a specific converter.
  3. Format specification: Starting with a colon (:), this defines how the object in a replacement field is converted to a string. Essentially, it controls the formatting of the replacement field object.
  4. Self-documenting expressions: These are replacement fields designed for print-debugging. They include an equals sign (=) following the field.
  5. Modifier: This is an unofficial term used to describe the combined use of conversion fields, format specifications, and self-documenting expressions. As there isn't an official umbrella term for all these three ideas, "modifier" is a suitable descriptor.

These terms might seem abstract and confusing without context. However, through detailed examples, their functions and applications will become clear.

Demonstrating Python's f-strings with Examples

Let's set the stage with a few variables:

>>> pet_name = "Whiskers"
>>> prices = [4.50, 2.75, 3.15, 7]

Our first f-string example involves a single replacement field:

>>> print(f"My pet's name is {pet_name}.")
My pet's name is Whiskers.

Here's an f-string with two replacement fields:

>>> print(f"My pet's name is {pet_name} and it's {len(pet_name)} letters long.")
My pet's name is Whiskers and it's 8 letters long.

The next example has one replacement field with a format specification (observe the colon symbol):

>>> print(f"The total price is ${sum(prices):.2f}")
The total price is $17.40

Here is an f-string with two replacement fields; the second one includes a format specification (again, notice the colon):

>>> s_count = pet_name.count("s")
>>> print(f"My pet's name, {pet_name}, contains {s_count/len(pet_name):.0%} letter s.")
My pet's name, Whiskers, contains 25% letter s.

This example showcases the use of a conversion field in an f-string (note the exclamation mark symbol):

>>> print(f"The variable 'pet_name' contains {pet_name!r}.")
The variable 'pet_name' contains 'Whiskers'.

Finally, we have an f-string with two self-documenting expressions (see the equal sign following each replacement field):

>>> print(f"Variables: {pet_name=}, {prices=}")
Variables: pet_name='Whiskers', prices=[4.5, 2.75, 3.15, 7]

Now, you might be wondering, how exactly do these various f-string modifiers function and what can you achieve with them?

Well, string formatting operates differently depending on the types of objects you're dealing with. Let's delve into some common objects you might find yourself using with string formatting.

Formatting Numbers with Python's f-strings

Using format specifications with numbers is quite common in Python, particularly when you want to control the number of digits displayed after the decimal point. This technique employs fixed-point notation (a term you don't necessarily have to remember, but can be handy in understanding this concept).

Let's use two numbers for illustration:

>>> from math import e
>>> num = 7

The .Nf format specifier, where N is a whole number, allows us to format a number to show N digits after the decimal point. Let's see this in action:

To display one digit after the decimal point, we use .1f:

>>> print(f"One digit: {num:.1f} and {e:.1f}")
One digit: 7.0 and 2.7

For two digits after the decimal point, .2f is used:

>>> print(f"Two digits: {num:.2f} and {e:.2f}")
Two digits: 7.00 and 2.72

And for zero digits after the decimal point, .0f comes into play:

>>> print(f"Zero digits: {num:.0f} and {e:.0f}")
Zero digits: 7 and 3

This fixed-point notation format specification rounds the number in the same manner as Python's built-in round function. So you can be sure of consistent and predictable output.

Understanding Zero-padding with Python's f-strings

Zero-padding is another useful feature offered by Python's f-string format specifications. It allows us to format numbers to have a specific length by padding them with zeros on the left-hand side. This is done using the 0Nd format specifier, where N is a whole number representing the desired length of the number.

For example, consider this list of planets and their order from the sun:

>>> planets = [(1, "Mercury"), (2, "Venus"), (3, "Earth"), (10, "Saturn")]
>>> for n, planet in planets:
...     print(f"{n:02d}. {planet}")
01. Mercury
02. Venus
03. Earth
10. Saturn

This is particularly handy when you want to align numbers in a column or table format, creating a neat and readable display.

However, it's important to note a peculiarity: while 0N is a shorthand for 02d with numbers, it behaves differently with strings. With strings, it appends zeros to the end rather than the beginning. Moreover, using 0Nd with strings raises an exception, as it's designed to work with integer types.

>>> n = 5
>>> print(f"{n:04}")
>>> text = 'Hello'
>>> print(f"{text:07}")
>>> print(f"{text:04d}")
Traceback (most recent call last):
  File "", line 1, in 
ValueError: Unknown format code 'd' for object of type 'str'

For these reasons, using 0Nd can be considered more explicit and less prone to errors, particularly if your code involves both numbers and strings. The trailing d in 0Nd stands for decimal integer, meaning it only works with integer types.

Formatting Percentages with Python's f-strings

Python's f-string also supports a handy format specifier for representing numbers as percentages. By using the .N% format specifier (where N is a whole number), we can format a number as a percentage. This specifier multiplies the number by 100, formats it to display N digits after the decimal point, and appends a % sign to it.

Consider these voting results:

>>> yes_votes = 530
>>> total_votes = 1000
>>> print(f"The 'Yes' votes constitute {yes_votes/total_votes:.0%} of the total votes.")
The 'Yes' votes constitute 53% of the total votes.

We can get more precise by showing one decimal place:

>>> print(f"The 'Yes' votes constitute {yes_votes/total_votes:.1%} of the total votes.")
The 'Yes' votes constitute 53.0% of the total votes.

Or two decimal places:

>>> print(f"The 'Yes' votes constitute {yes_votes/total_votes:.2%} of the total votes.")
The 'Yes' votes constitute 53.00% of the total votes.

Now, let's consider a more complex scenario, where we want to compare the percentage of votes of three different parties in an election:

>>> party_votes = [('Red Party', 1267), ('Blue Party', 2384), ('Green Party', 856)]
>>> total_votes = sum(votes for party, votes in party_votes)

>>> for party, votes in party_votes:
...     print(f"The {party} received {votes/total_votes:.2%} of the total votes.")
The Red Party received 27.53% of the total votes.
The Blue Party received 51.81% of the total votes.
The Green Party received 18.60% of the total votes.

This code calculates the percentage of votes each party received in the election and prints it with a precision of two decimal places.

Adding Thousands Separators with Python's f-strings

In Python, the f-string format specification allows you to represent large numbers with thousands separators. This can make such numbers more human-readable.

Firstly, the , format specifier allows us to format a number to include commas as a thousands separator. Consider this example:

>>> num_books = 45679300
>>> print(f"The library holds {num_books:,} books.")
The library holds 45,679,300 books.

Alternatively, you might prefer to use an underscore as a thousands separator. This can be achieved using the _ format specifier:

>>> print(f"The library holds {num_books:_} books.")
The library holds 45_679_300 books.

Python also supports locale-aware formatting, which uses a period, comma, or another appropriate thousands separator based on the locale. This can be done using the n format specifier. Here's an example:

>>> import locale
>>> locale.setlocale(locale.LC_NUMERIC, "de_DE.utf-8")
>>> print(f"The library holds {num_books:n} books.")
The library holds 45.679.300 books.
>>> locale.setlocale(locale.LC_NUMERIC, "en_GB.utf-8")
>>> print(f"The library holds {num_books:n} books.")
The library holds 45,679,300 books.

In the first case, we set the locale to Germany (de_DE), which uses periods as thousands separators. In the second case, we set the locale to Great Britain (en_GB), which uses commas.

Customizing the Format of Datetime Objects

The f-string formatting in Python is not only restricted to string and numerical types. It can also be applied to other types of objects, such as those from the datetime module.

For example, Python's datetime.datetime class (as well as datetime.date and datetime.time) supports string formatting. It uses the same syntax as the strftime method on these objects, allowing us to customize the date and time format. Consider the following instance:

>>> import datetime
>>> historical_event = datetime.date(1492, 10, 12)
>>> print(f"On {historical_event:%B %d, %Y}, Columbus reached the New World.")
On October 12, 1492, Columbus reached the New World.

It is often more convenient to use string formatting for datetime.date and datetime.datetime objects than to use their strftime method. For instance, when dealing with both date and time:

>>> momentous_event = datetime.datetime(1969, 7, 20, 20, 17, 40)
>>> print(f"The Apollo 11 moon landing occurred on {momentous_event:%B %d, %Y, %H:%M:%S}.")
The Apollo 11 moon landing occurred on July 20, 1969, 20:17:40.

Python's ipaddress module also supports custom string formatting for IPv4Address and IPv6Address objects. Furthermore, third-party libraries can add their own custom string formatting support by adding a __format__ method to their objects.

Objects built-in to Python that support custom string formatting are: numbers, strings, datetime and date objects, and IPv4Address and IPv6Address objects.

Self-documenting Expressions and Debugging Aids

In Python 3.8, a handy feature called self-documenting expressions was added to the list of f-string capabilities. By appending an equals sign (=) at the end of your replacement fields, you generate a self-documenting expression.

>>> animal = "elephant"
>>> color = "grey"
>>> print(f"Info: {animal=}, {color=}")
Info: animal='elephant', color='grey'

With self-documenting expressions, the output string includes both the original replacement field expression and its result, separated by an equals sign. By default, the repr function is used to represent the result.

However, you can modify this by adding an explicit !s for a string representation (or !a for the ASCII representation).

>>> print(f"Info: {animal=!s}")
Info: animal=elephant

Although, the repr format is typically the preferred choice when using self-documenting expressions, especially when debugging.

Just as with standard replacement fields, any expression can be used in your self-documenting expression and the entire expression will be displayed:

>>> items = [5, 7, 10]
>>> print(f"{sum(items)=}")

Additionally, a format specifier can be included to format the result of the expression:

>>> print(f"{sum(items)=:02d}")

It's crucial to remember that the equals sign should be placed at the end of the replacement field, but it must be before any format specifier or conversion field, if present.

Moreover, you can also add spaces around the equals sign to reflect the same in the resulting string:

>>> print(f"{items = }")
items = [5, 7, 10]
>>> print(f"{sum(items) = :02d}")
sum(items) = 22

In today's discussion, we explored the various aspects of Python's f-strings, from their basic usage to advanced formatting techniques:

  • We learned how to utilize f-string formatting to include variables and expressions within strings, while simultaneously controlling the format of the embedded data.
  • We examined specific number formatting techniques, including setting the precision of decimal numbers, using zero-padding to control the total length of numbers, and formatting numbers as percentages.
  • We studied how to use f-strings to control the display of large numbers, using comma or underscore as thousands separators, and we delved into locale-aware formatting.
  • We also explored how to format datetime objects with f-strings.
  • Lastly, we examined the utility of self-documenting expressions in f-strings for debugging purposes. This feature allows for displaying both an expression and its result within the string.

Overall, we emphasized the power and versatility of f-strings in Python, which extend beyond mere string interpolation and allow for intricate control over the presentation of various data types.


Popular posts from this blog

Drawing Tables with ReportLab: A Comprehensive Example

Blog Topics

DataFrame groupby agg style bar