Using the Python format() Method for String Formatting
Learn how Python's format() method works: placeholders, format specifiers, alignment, number formatting, object and dictionary access, and choosing it over f-strings.
Python offers two closely related formatting tools: the built-in format(value, format_spec) function and the str.format() method. This article focuses on the str.format() method, which lets you insert values into placeholders in a template string and control their appearance. It is a flexible alternative to older %-style formatting and remains useful when the format string itself must be dynamic.
How the format() Method Works
The str.format() method is called on a string containing replacement fields, each enclosed in curly braces {}. Fields without an index or name consume the next positional argument. You can also use positional indices or keyword names to map values explicitly.
name = 'Ada' age = 36 print('{} is {} years old'.format(name, age)) print('{0} is {1} years old'.format(name, age)) print('{name} is {age} years old'.format(name=name, age=age))
All three produce the same output. The first relies on implicit order, the second uses explicit positional indices, and the third uses keyword arguments. Mixing explicit positional indices and keyword arguments in the same format string is allowed but can make the template harder to read, so it is usually better to choose one style.
The Built-in format() Function
If you need to format a single value, the built-in format(value, format_spec) function is often simpler than a one-placeholder template. It uses the same format specifiers described in the next section.
print(format(3.14159, '.2f')) # 3.14 print(format(255, '06x')) # 0000ff
The first example formats a float to two decimal places. The second formats an integer as lowercase hexadecimal in a zero-padded field of width 6. The specifiers are the same ones you would use after the colon inside a str.format() replacement field.
Format Specifiers and the Mini-Language
Each replacement field in a str.format() template can include a format specifier after a colon. The specifier controls alignment, width, numeric precision, and type conversion. The general syntax is {argument:format_specifier}.
value = 42 print('{:>10}'.format(value)) # right-aligned in width 10 print('{:<10}'.format(value)) # left-aligned print('{:^10}'.format(value)) # centered print('{:010}'.format(value)) # zero-padded to width 10
The alignment characters are > for right, < for left, and ^ for center. A fill character can be placed before the alignment character, as in {:*^10} to center with asterisks. The width is an integer that sets the minimum field width; the value is padded if shorter.
Formatting Numbers and Floats
Numeric types have their own set of format codes. For integers, d is the default, but you can use x for hexadecimal, o for octal, or b for binary. For floats, f uses fixed-point notation, e uses scientific notation, and g chooses a general representation based on precision and exponent.
pi = 3.14159265 print('{:.2f}'.format(pi)) # 3.14 print('{:e}'.format(pi)) # 3.141593e+00 print('{:+.2f}'.format(pi)) # +3.14 print('{:,.2f}'.format(1234567.891)) # 1,234,567.89
The precision after the dot specifies the number of digits after the decimal point for f and e, or the total number of significant digits for g. The comma adds thousands separators, which is useful for large numbers in reports or logs.
Accessing Object Attributes and Dictionary Keys
Format fields can reference attributes or dictionary keys using the dot and bracket syntax. This is especially handy when formatting objects or mapping data without manually extracting each value.
class User: def __init__(self, name, role): self.name = name self.role = role user = User('Alice', 'admin') print('{0.name} has role {0.role}'.format(user)) data = {'name': 'Bob', 'score': 95} print('{name} scored {score}'.format(**data)) print('{0[name]} scored {0[score]}'.format(data))
The ** operator unpacks a mapping into keyword arguments, which is convenient when the keys are valid Python identifiers. The bracket syntax lets you access keys directly without unpacking. Attribute access works on any object that exposes the named attribute. Both forms keep the format string concise and avoid manual concatenation.
Comparing format() with f-strings
Since Python 3.6, f-strings provide a more concise way to embed expressions directly in string literals. For most static strings, f-strings are preferred because they are more readable and often avoid the extra work of parsing a format template. The format() method still matters when the format template is built at runtime, such as when it comes from configuration or user input.
name = 'Carol' # f-string print(f'Hello {name}') # format() print('Hello {}'.format(name))
An f-string literal is evaluated when the line runs, so it does not leave a reusable template behind. If you need to apply the same template to many records, format() is a practical standard tool. For example, a reporting tool might read a format pattern from a config file and apply it to each data row.
Common Pitfalls and Edge Cases
One frequent mistake is forgetting to escape literal braces. To include a brace in the output, double it: {{ and }}. Another issue is missing a required argument, which raises an IndexError or KeyError depending on the field type.
print('{{}}'.format()) # outputs {} # print('{}'.format()) # IndexError: Replacement index 0 out of range
Format specifiers are strict: an invalid code raises ValueError. For example, '{:q}'.format(5) fails because q is not a valid type. Always validate format strings that come from external sources, because a malformed specifier will raise an exception and stop the program.
Performance and Maintainability Considerations
F-strings can have less runtime overhead than format(), because the compiler processes an f-string literal at compile time while format() parses the format string at runtime. In tight loops that build many strings, this difference may matter. However, the performance gap is rarely significant for typical application code. The larger maintainability concern is that format() separates the template from the values, which can make long strings harder to read. F-strings keep the expression next to the placeholder, improving readability for static templates.
When you need a dynamic template, format() is the usual standard option. In that case, document the expected format specifiers and validate them before use. Also consider using named fields to make the template self-explanatory, especially when the same value appears multiple times.
template = '{name} (id: {id:05d})' for record in records: print(template.format(**record))
This pattern keeps the formatting logic centralized and avoids duplicating the same specifier across many lines. If the format needs to change, you update one template instead of every call site.