Python f-string alignment: padding and positioning text
python f string alignment: Learn how to align text in Python f-strings using format specifiers for width, fill, and positioning.
Python f-strings support the same format specification mini-language used by str.format(). That mini-language includes alignment and width specifiers, which let you pad and position text inside a fixed-width field. This article covers the syntax, common usage patterns, and edge cases for python f string alignment.
The alignment format specifier in f-strings
Inside an f-string, the expression after the colon is the format specifier. The general form for alignment is:
{value:[[fill]align][width]}
The align character can be < (left), > (right), or ^ (center). The width is an integer that defines the minimum field width. If the formatted value is shorter than the width, it is padded with spaces (or a custom fill character) to match the width.
name = "Ada" print(f"{name:<10} Lovelace") # left-aligned in 10 chars print(f"{name:>10} Lovelace") # right-aligned in 10 chars print(f"{name:^10} Lovelace") # centered in 10 chars
Output:
Ada Lovelace
Ada Lovelace
Ada Lovelace
The alignment character is placed immediately after the fill character (if any) and before the width. If you omit the alignment, strings are left-aligned by default, and numbers are right-aligned by default. This default matters when you mix types in a table.
Controlling width and fill characters
The width can be any non-negative integer. If the value is longer than the width, the width is ignored and the value is printed in full. The fill character is a single character placed before the alignment specifier. It is used to pad the field instead of spaces.
price = 42 print(f"{price:*>10}") # '********42' print(f"{price:0>10}") # '0000000042' print(f"{price:->10}") # '--------42'
The fill character must be specified together with an alignment; you cannot use a fill character without an alignment. For example, {price:*10} is invalid because the parser expects an alignment after the fill.
Aligning numbers and strings with variables
You can use variables for the width, which makes alignment dynamic. This is useful when the field width depends on runtime data, such as the longest entry in a list.
items = ["apple", "banana", "cherry"] width = max(len(item) for item in items) for item in items: print(f"{item:>{width}}")
This right-aligns all items to the same width, producing a neat column. You can also use an expression for the width directly inside the f-string:
for item in items: print(f"{item:>{max(len(i) for i in items)}}")
But be careful: the expression is evaluated for each iteration, which may be inefficient for large lists. Computing the width once outside the loop is usually cleaner.
Combining alignment with other format specifiers
Alignment works with other format specifiers, such as precision, sign, and type. The order is: fill, align, sign, zero-padding, width, grouping, precision, type. For example, to align a floating-point number to 10 characters with two decimal places:
value = 1234.5678 print(f"{value:>10.2f}") # ' 1234.57' print(f"{value:^10.2f}") # ' 1234.57 '
The width applies to the entire formatted output, including the decimal point and fractional digits. If you combine zero-padding (0) with alignment, the zero-padding is applied after the sign and before the digits, but alignment still controls the overall field width.
print(f"{value:010.2f}") # '0001234.57' (zero-padded, no explicit alignment) print(f"{value:>010.2f}") # '0001234.57' (right-aligned with zero fill)
Note that >010 is equivalent to 0>10 in effect, but the explicit fill character and alignment make the intent clearer.
Common mistakes and edge cases
One frequent mistake is forgetting the colon or using the wrong alignment character. The alignment characters are <, >, and ^. Using - or | as alignment will raise a ValueError. Another mistake is placing the width before the alignment, such as {name:10<}. The correct order is {name:<10}.
When the width is smaller than the value's natural length, the value is not truncated. For example, f"{'hello':>3}" produces 'hello', not 'llo'. If you need truncation, use a precision specifier for strings, but note that precision truncates from the end, not from the start.
word = "supercalifragilistic" print(f"{word:.5}") # 'super' (truncated to 5 chars) print(f"{word:>5.5}") # 'super' (right-aligned, then truncated)
Another edge case is aligning multi-line strings. The alignment specifier applies to the entire string as a single field, not to each line. If you need to align each line separately, you must split and format each line individually.
Performance and maintainability considerations
F-strings are evaluated at runtime, and the alignment operation itself is cheap—it's essentially a string padding operation. For typical output formatting, the overhead is negligible. However, when formatting thousands of rows in a loop, computing the width dynamically inside the f-string can add unnecessary repeated work. Precompute the width outside the loop to keep the code efficient and readable.
Alignment is most valuable in log messages, CLI tables, and reports where consistent column widths improve readability. Using f-string alignment keeps the formatting logic inline, which is often more maintainable than using str.format() or %-style formatting because the expression and its format specifier stay together. If you need to reuse the same format specifier in multiple places, consider defining a constant string that contains the specifier, or use a helper function to avoid duplication.
Advanced: nested format specifiers and dynamic alignment
You can nest f-string expressions inside the format specifier to create fully dynamic alignment. For example, you can use a variable for the fill character and the alignment direction:
fill = '-' align = '>' width = 12 value = 123 print(f"{value:{fill}{align}{width}}") # '---------123'
The nested braces {fill}{align}{width} are evaluated first, producing the format specifier ->12. This technique is useful when you need to build a format specifier at runtime, such as when the alignment direction depends on locale or user preference.
You can also combine nested specifiers with other format options:
precision = 3 print(f"{value:{fill}{align}{width}.{precision}f}")
This produces a right-aligned, dash-filled field of width 12 with three decimal places. Nested specifiers add flexibility but can reduce readability, so use them sparingly and document the intent clearly.
When working with dictionaries, you can align values by key in a table:
data = {"name": "Ada", "role": "Engineer", "years": 37} width = max(len(k) for k in data) for key, val in data.items(): print(f"{key:>{width}}: {val}")
This pattern is common for generating aligned output from structured data without pulling in a third-party library. The same technique works with lists of tuples or any iterable of key-value pairs.