Python F-String Padding for Clean Output
python f string padding: Learn how to pad and align values inside Python f-strings using width, alignment codes, and fill characters, including dynamic widths and nume...
python f string padding requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
Python f-string padding controls the minimum width of a formatted value and the character used to fill the remaining space. The syntax lives inside the format specifier after the colon: {value:width} pads a value to the given width, and {value:fill align width} adds a fill character and an alignment rule. The same specifier also combines with numeric precision and sign handling.
The Padding Syntax in F-Strings
The most basic form of padding is {value:width}, where width is the minimum number of characters the formatted result should occupy. If the value is shorter than the width, Python fills the remaining space with spaces.
name = "Ada" print(f"|{name:10}|")
This prints |Ada |. The string "Ada" occupies three characters, and seven spaces are added to reach the requested width of ten. Padding never truncates: a value longer than the width is printed in full.
The width applies to the formatted representation of the value. For a number, the width counts the digits, the decimal point, and any sign or exponent characters. For a string, it counts every character.
Alignment: Left, Right, and Center
By default, strings are left-aligned inside the padded area and numbers are right-aligned. You can control alignment explicitly with one of three alignment codes placed after the fill character position and before the width.
<left-aligns the value>right-aligns the value^centers the value
name = "Ada" print(f"|{name:<10}|") print(f"|{name:>10}|") print(f"|{name:^10}|")
The output is:
|Ada |
| Ada|
| Ada |
The same codes work for numbers, but the default differs. f"{42:10}" right-aligns the number, so the result is 42. If you want a number left-aligned, write f"{42:<10}".
| Alignment | Code | Example | Result |
|---|---|---|---|
| Left | < | f"{'x':<5}" | 'x ' |
| Right | > | f"{'x':>5}" | ' x' |
| Center | ^ | f"{'x':^5}" | ' x ' |
Center alignment is useful for column headers in text output. Note that when the padded width is even and the value length is odd, the extra space goes to the right side.
Choosing a Fill Character
The space is only the default fill character. You can specify any single character to occupy the padded area by placing it immediately before the alignment code.
print(f"{'menu':*^12}") print(f"{42:0>6}")
The first example produces ****menu****, and the second produces 000042. The fill character must be a single character; using a multi-character string raises a ValueError.
A common pattern is zero-padding numbers with 0 as the fill character. f"{7:03}" produces 007. For numbers, the 0 fill character has a special interaction with sign handling: f"{-7:05}" produces -0007, where the sign stays in front and the zeros fill the remaining width.
Padding Numbers Without Breaking Formatting
Padding and numeric formatting share the same format specifier, so you can combine width, alignment, fill, sign, and precision in one expression.
price = 12.5 print(f"{price:08.2f}") print(f"{price:>10.2f}") print(f"{price:+010.2f}")
The first line produces 000012.50. The width of eight includes the decimal point and both fractional digits. The second line right-aligns the value in a ten-character field. The third line adds an explicit + sign and pads to ten characters, giving +0000012.50.
When combining these, the order matters: fill character, alignment code, sign, width, precision, and type. Writing f"{price:08.2f}" means fill with 0, no explicit alignment (so right alignment for numbers), width eight, two decimal places, float type. Changing the order, such as f"{price:8.02f}", changes the meaning because 0 is now interpreted as a fill character rather than part of the width.
Dynamic Width from Variables and Expressions
The width does not have to be a literal. You can nest a format specifier inside the braces to pull the width from a variable or expression.
width = 12 name = "Grace" print(f"|{name:{width}}|")
This prints |Grace |. The inner {width} is evaluated first and then used as the width for the outer value. The same nesting works for fill characters and alignment codes.
align = "^" fill = "-" print(f"{name:{fill}{align}{width}}")
This produces ----Grace----. Dynamic width is useful when the column size comes from configuration, terminal width, or the longest value in a dataset. You can compute the width from the data itself:
rows = ["alpha", "beta", "gamma"] col_width = max(len(r) for r in rows) for r in rows: print(f"{r:<{col_width}}")
This keeps columns aligned without hard-coding a width that might be too small for future data.
Common Padding Mistakes and Edge Cases
The most frequent mistake is expecting padding to truncate. f"{'long text':5}" returns long text unchanged; the width is a minimum, not a maximum. If you need truncation, slice the value first or use the precision specifier for strings: f"{'long text':.5}" gives long t.
Another common error is using a multi-character fill string. f"{'x':ab<5}" raises ValueError: Invalid format specifier. The fill must be exactly one character.
Zero-padding negative numbers behaves differently from what many developers expect. f"{-7:05}" produces -0007, not 000-7. The sign is placed before the zeros. If you want the sign to appear after the padding, you need explicit alignment: f"{-7:0>5}" produces 000-7, but this is rarely what you want in numeric columns.
Alignment defaults also cause confusion. A string and a number with the same specifier produce different alignment:
print(f"{'7':>5}") # ' 7' print(f"{7:>5}") # ' 7' print(f"{'7':5}") # '7 ' print(f"{7:5}") # ' 7'
The string defaults to left alignment, the number to right alignment. When you rely on the default, the same specifier can produce visually inconsistent output across types.
Performance and Maintainability of Padding
Padding in f-strings is evaluated at runtime and adds a small allocation cost proportional to the padded width. For typical output, this cost is negligible. The main maintainability concern is readability: a specifier like {value:0>+12.2f} is compact but hard to parse. When the format becomes complex, extract it into a named constant or a small helper function.
def money(value: float) -> str: return f"{value:>12.2f}"
This keeps the formatting logic in one place and prevents the same specifier from being duplicated across logging statements, reports, and user-facing messages. It also makes the alignment and width decisions explicit when the format changes.
Avoid building padded strings manually with string multiplication when an f-string specifier expresses the same intent. " " * (10 - len(name)) + name is harder to read and breaks when name is longer than the width. The format specifier handles that case naturally by leaving the value unchanged.
The nested width syntax has a small readability cost as well. When the width comes from a variable, the expression f"{name:{width}}" is clear, but deeply nested specifiers such as f"{name:{fill}{align}{width}}" become difficult to audit. Prefer computing the full specifier string separately when it grows beyond a single nested value.