Python f-String Number Formatting
python f string number formatting: Learn Python f-string number formatting: precision, thousands separators, alignment, signs, and format specifiers for integers, floa...
Python f-strings provide a concise way to embed expressions in string literals, and their format specifiers make number formatting straightforward. This article covers the essential patterns for python f string number formatting, from precision control to alignment and locale-aware separators.
Basic Number Formatting with f-strings
An f-string is a string literal prefixed with f or F. Inside the braces, you can place a variable, an expression, or a function call. The simplest use is to embed a number directly:
value = 42 print(f"The answer is {value}") # The answer is 42
For floating-point numbers, the default representation is often sufficient, but when you need to control how the number appears, you add a format specifier after a colon inside the braces. The format specifier follows the same syntax as the format() method, which is defined by the Format Specification Mini-Language.
Controlling Precision and Rounding
The most common formatting need is to limit the number of decimal places. Use the .Nf specifier, where N is the number of digits after the decimal point:
pi = 3.141592653589793 print(f"{pi:.2f}") # 3.14 print(f"{pi:.4f}") # 3.1416
The value is rounded to the requested precision. For example, 2.675 formatted with .2f yields 2.67 due to floating-point representation, but in general the rounding follows the same rules as Python's round() for floats. If you need exact decimal arithmetic, use the decimal module.
For scientific notation, use .Ne or .NE:
large = 1234567.89 print(f"{large:.2e}") # 1.23e+06
The g specifier chooses between fixed-point and scientific notation based on the exponent, which is useful for compact output.
Thousands Separators and Locale-Aware Formatting
Large numbers are easier to read with separators. Use a comma or underscore directly in the format specifier:
population = 8_000_000_000 print(f"{population:,}") # 8,000,000,000 print(f"{population:_}") # 8_000_000_000
The underscore separator works with integers and floats, and it is often used in source code to improve readability. For locale-specific separators, such as spaces or periods, you need the locale module:
import locale locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8') print(f"{population:n}") # 8.000.000.000 (in German locale)
The n specifier applies the current locale's digit grouping and decimal separator. This is useful for internationalized applications, but it depends on the locale data being installed on the system.
Alignment, Padding, and Sign Display
Format specifiers can control the minimum field width, alignment, and padding characters. The general syntax is [[fill]align][sign][#][0][width][,][.precision][type].
To set a minimum width, include a number after the alignment character. For example, {value:10} right-aligns the value in a field of width 10. Use < for left alignment, ^ for center, and > for right (the default for numbers).
score = 95 print(f"{score:<10}|") # 95 | print(f"{score:>10}|") # 95| print(f"{score:^10}|") # 95 |
You can also pad with zeros for fixed-width numeric output, which is common in logs and serial numbers:
order_id = 42 print(f"{order_id:05d}") # 00042
The 0 flag pads with zeros. For signed numbers, you can force the sign to always appear:
temp = -5 print(f"{temp:+d}") # -5 print(f"{temp: d}") # "-5" with a leading space for positive numbers
Using + displays a plus sign for positive numbers, while a space reserves a position for the sign so positive and negative values align vertically.
Formatting Different Numeric Types
F-strings support all the numeric types defined in the format specification. For integers, you can display values in binary, octal, or hexadecimal:
number = 255 print(f"{number:b}") # 11111111 print(f"{number:o}") # 377 print(f"{number:x}") # ff print(f"{number:X}") # FF
For floats, the main types are f for fixed-point, e for exponent, and g for general. You can also format complex numbers, though the behavior is less common:
c = 3 + 4j print(f"{c:.2f}") # (3.00+4.00j)
The # flag adds a prefix for alternate forms, such as 0x for hexadecimal or 0b for binary:
print(f"{number:#x}") # 0xff print(f"{number:#b}") # 0b11111111
Common Mistakes and Edge Cases
A frequent error is forgetting the colon before the format specifier. Without it, the expression is evaluated but no formatting is applied. Another mistake is using the % formatting syntax inside an f-string, which does not work.
When working with None or missing values, f-strings will raise a TypeError if you try to format them with a numeric specifier. Use a conditional expression or a fallback value:
value = None print(f"{value if value is not None else 0:.2f}") # 0.00
Also, be aware that f-strings are not templates. They are evaluated at runtime, so you cannot store an f-string and reuse it with different values. If you need a reusable template, use str.format() or string.Template.
Another edge case is the interaction between width and precision. The width includes the decimal point and the sign, so {value:8.2f} will pad the entire formatted number to 8 characters, not just the integer part.
Performance and Readability Considerations
F-strings are evaluated at runtime, but the overhead is minimal compared to other formatting methods. In a tight loop, the difference between f-strings and str.format() is negligible for most applications. The main advantage of f-strings is readability: the expression is placed directly in the string, making the code easier to understand.
However, if you are formatting the same value repeatedly with the same specifier, you might consider precomputing the formatted string if the value does not change. For dynamic formatting where the specifier itself is variable, you can use the format() method or format_map() to apply a format string from a dictionary.
When performance is critical, remember that f-strings do not add extra overhead beyond the expression evaluation and the formatting itself. They are as fast as the underlying format() call, and often faster because the compiler can optimize the string construction.
For maintainability, keep format specifiers simple and avoid complex nested expressions inside the braces. If a formatting rule is reused in many places, extract it into a function that returns the formatted string.