Back to Blog
Python

Python String Format: f-Strings and format()

python string format: Learn how to format strings in Python using f-strings, str.format(), and format specifiers for alignment, padding, numbers, and custom objects.

f-stringsstr.formatformat specifiersstring interpolationPython syntax
Illustration of Python string formatting showing f-string interpolation and format specifiers

When you search for python string format, you are likely deciding which of the three formatting APIs to use in your code: the % operator, the str.format() method, or f-strings. Each has different syntax, capabilities, and tradeoffs. This article explains how they work, when to use each, and how to control alignment, padding, and number formatting with format specifiers.

The Three String Formatting APIs in Python

Python offers three distinct ways to format strings. The oldest is %-formatting, inherited from C's printf style. It uses %s, %d, %f placeholders and a tuple or dictionary of values. While still functional, it is verbose and less flexible than the alternatives.

The str.format() method, introduced in Python 2.6, uses curly braces {} as placeholders. It supports positional and keyword arguments, and it can access object attributes and dictionary keys directly inside the braces.

F-strings, added in Python 3.6, combine the placeholder syntax of str.format() with inline expressions. An f-string is a string literal prefixed with f or F; expressions inside {} are evaluated at runtime and inserted into the string. Because the expression is written directly in the literal, f-strings are more readable and often faster than the other two approaches.

f-Strings: The Default Choice for Most Code

F-strings are the recommended way to format strings in modern Python. They are concise, support arbitrary expressions, and keep the format template close to the data being inserted.

name = "Ada" score = 92 message = f"{name} scored {score} points" print(message)

You can call methods, access list indices, or evaluate arithmetic inside the braces:

items = ["apple", "banana", "cherry"] print(f"First item: {items[0].upper()}") print(f"Sum: {sum([1, 2, 3])}")

F-strings also support format specifiers, which control alignment, width, precision, and number representation. The specifier follows the expression after a colon:

value = 12.34567 print(f"{value:.2f}") # 12.35

Because f-strings are evaluated at runtime, they work with any expression that can appear in Python code. They are the clearest choice for most formatting needs, especially when the format template is known at write time.

The str.format Method for Dynamic Formatting

str.format() remains useful when the format string is not known until runtime, for example when it comes from a configuration file or a user-provided template. The method accepts positional and keyword arguments:

template = "{name} has {count} items" print(template.format(name="Bob", count=3))

You can also use indexed placeholders to reuse arguments:

print("{0} and {1} and {0}".format("a", "b"))

str.format() supports the same format specifiers as f-strings. The main difference is that the values are passed separately, which makes it easier to build templates dynamically.

One limitation is that str.format() cannot access local variables implicitly; you must pass them explicitly. This can make the call site verbose when many values are involved. In such cases, f-strings are simpler.

Format Specifiers: Alignment, Padding, and Number Formatting

The format specifier mini-language controls how values are presented. It appears after the colon in both f-strings and str.format(). The general syntax is:

[[fill]align][sign][#][0][width][,][.precision][type]

Common uses include:

  • Alignment: < left, > right, ^ center. A fill character can precede the alignment symbol.
  • Width: minimum field width in characters.
  • Sign: + forces a plus sign for positive numbers, - is the default, and a space leaves a gap.
  • Zero padding: 0 before the width pads with zeros.
  • Number types: d for integers, f for fixed-point floats, e for scientific notation, % for percentage.
print(f"{42:>5}") # ' 42' print(f"{42:<5}") # '42 ' print(f"{42:^5}") # ' 42 ' print(f"{42:05}") # '00042' print(f"{3.14159:.2f}") # '3.14' print(f"{0.25:.0%}") # '25%'

For integers, you can also use comma or underscore as thousands separators:

print(f"{1234567:,}") # 1,234,567 print(f"{1234567:_}") # 1_234_567

These specifiers work identically in str.format(). Understanding them lets you produce consistent, well-aligned output for reports, logs, and user interfaces.

Formatting Dates, Times, and Custom Objects

Format specifiers are not limited to built-in types. You can define how your own objects are formatted by implementing the __format__ method. This method receives the specifier string and must return a string representation.

class Temperature: def __init__(self, celsius): self.celsius = celsius def __format__(self, spec): if spec == "f": return f"{self.celsius:.1f}°C" return f"{self.celsius}°C" temp = Temperature(21.5) print(f"{temp:f}") # 21.5°C

Similarly, datetime objects support specifiers like %Y-%m-%d when passed through strftime, but they also work with the format specifier :%H:%M inside f-strings:

from datetime import datetime now = datetime.now() print(f"{now:%Y-%m-%d %H:%M}")

This integration makes it easy to produce formatted timestamps without calling strftime separately.

Performance and Readability Tradeoffs

F-strings are generally faster than str.format() because they are processed at compile time into efficient bytecode. The expression is evaluated and converted to a string using format(), but the parsing of the template happens once, not on every call. str.format() must parse the template string each time it is invoked, which adds overhead.

For most applications the difference is negligible, but in tight loops that format thousands of strings, f-strings can reduce CPU usage. More importantly, f-strings improve readability because the values are inline, making it easier to see what will be inserted.

However, str.format() has a clear advantage when the template is dynamic. If you need to choose the format at runtime, perhaps from a data-driven configuration, str.format() is the only option among the two modern APIs. The % operator also supports dynamic templates, but it is less flexible.

Common Pitfalls and Compatibility Notes

F-strings require Python 3.6 or later. If you support older versions, you must use str.format() or %-formatting. Also, because f-strings evaluate expressions at runtime, they can have side effects if the expression calls a function that mutates state. Keep f-string expressions side-effect-free to avoid surprising behavior.

Escaping braces in f-strings and str.format() is a common source of errors. To include a literal { or }, double it:

print(f"{{literal}}") # {literal}

In str.format(), the same doubling applies. For nested format specifiers, you may need to use str.format() with a variable specifier, which is awkward with f-strings. For example, dynamic width:

width = 10 value = 42 print(f"{value:>{width}}") # works in f-strings

But if the specifier itself is built dynamically, str.format() is clearer:

spec = ">10" print("{:{}}".format(value, spec))

Another pitfall is using str.format() on a string that contains many braces, such as JSON or CSS. You must escape every brace, which quickly becomes unreadable. In those cases, consider f-strings or template substitution with a dedicated library.

Finally, be aware that %-formatting does not support the format specifier mini-language. It has its own conversion flags, which are more limited. For new code, prefer f-strings or str.format().

python string format: Practical Usage and Code Examples | RYUSLOG DEV