Formatter.convert_field / format_field

For '{x!r:>10}' vformat calls convert_field(x, 'r') and then format_field(result, '>10'). The defaults are repr/str/ascii and format(); overriding them adds conversions like !u or changes how specs are read.

Formatter methodPython 2.6+Live demo
Common call
def convert_field(self, value, conversion): if conversion == 'u': return str(value).upper() return super().convert_field(value, conversion)
Returns
the value that format_field will format
Replaces
pre-processing every value before calling str.format
Watch out
unknown conversions raise ValueError — always fall back to super()
Formatter.convert_field(valuevalue — convert_field: the object get_field found. format_field: the (converted) object to format.type: object · required, conversion) · Formatter.format_field(value, format_specformat_spec — The text after : with any nested {fields} already filled in; '' when there is none.type: str · required)
→ object · str

Demo

Live evaluation
Run the two steps by hand on a str value. Leave conversion empty for None.
Try:
Inputs
valuestra str value
convstr | None'r', 's', 'a' or empty
specstrformat spec, e.g. >12
Code
import string
f = string.Formatter()
f.format_field(f.convert_field('café', None), '*^10')
Result
'***café***'

!r and !a run before the spec, so the quotes (and the \xe9 escape that ascii() writes for é) are part of the text that gets padded. A str value has only the s presentation type, so :d fails even when the text is '42'. An unknown conversion reaches the base class and raises "Unknown conversion specifier x".

Parameters

NameTypeRequiredDescription
valueobjectyesconvert_field: the object get_field found. format_field: the (converted) object to format.
conversionstr | NoneyesThe character after ! — 'r', 's', 'a' by default — or None when the field has no conversion.
format_specstryesThe text after : with any nested {fields} already filled in; '' when there is none.

Return value

object · str — convert_field: the converted value (unchanged when conversion is None). format_field: the formatted text.

Common patterns

Format your own types without touching them
Intercept format_field for one type and delegate everything else.
import string
from decimal import Decimal

class Money(string.Formatter):
    def format_field(self, value, format_spec):
        if isinstance(value, Decimal) and format_spec == 'money':
            return f'${value:,.2f}'
        return super().format_field(value, format_spec)

Money().format('{0:money}', Decimal('1234.5'))
Escape every value for HTML
format_field runs for every field — the right place for output escaping.
import html, string

class HTMLFormatter(string.Formatter):
    def format_field(self, value, format_spec):
        return html.escape(super().format_field(value, format_spec))

HTMLFormatter().format("<p>{}</p>", "<script>")
None as an empty field
format(None, '>5') raises TypeError; map None to '' first.
import string

class Blank(string.Formatter):
    def format_field(self, value, format_spec):
        return super().format_field('' if value is None else value, format_spec)

Examples

1. convert_field with !r
import string string.Formatter().convert_field('hé', 'r')
Returns
"'hé'"
2. !a escapes non-ASCII
import string string.Formatter().convert_field('hé', 'a')
Returns
"'h\\xe9'"
3. No conversion: unchanged
import string string.Formatter().convert_field(3, None)
Returns
3
4. format_field is format()
import string string.Formatter().format_field(3.14159, '.2f')
Returns
'3.14'
5. Spec for an int
import string string.Formatter().format_field(42, '08b')
Returns
'00101010'
6. Dates use their own spec
import string from datetime import date string.Formatter().format_field(date(2026, 10, 4), '%d %b %Y')
Returns
'04 Oct 2026'
7. Spec on None fails
import string string.Formatter().format_field(None, '>5')
Returns
TypeError: unsupported format string passed to NoneType.__format__

Pitfalls

1. Not falling back to super()
An override that only handles its own conversion breaks !r, !s and !a.
own conversion only
import string
class U(string.Formatter):
    def convert_field(self, value, conversion):
        return str(value).upper() if conversion == 'u' else value
U().format('{0!r}', 'x')
'x'
delegate the rest
import string
class U(string.Formatter):
    def convert_field(self, value, conversion):
        if conversion == 'u':
            return str(value).upper()
        return super().convert_field(value, conversion)
U().format('{0!r}', 'x')
"'x'"
2. Expecting the same error text as str.format
Formatter.convert_field builds its message with str(), so a non-ASCII conversion character is shown as is; str.format (in C) shows it escaped.
str.format
'{0!é}'.format('a')
ValueError: Unknown conversion specifier \xe9
Formatter
import string
string.Formatter().format('{0!é}', 'a')
ValueError: Unknown conversion specifier é

When to use

Use it
  • New conversion flags (!u, !l, !j for JSON …) — convert_field
  • Custom spec mini-languages, per-type formatting or escaping every value — format_field
Reach for something else
  • Formatting one class of your own → give that class a __format__ method; str.format and f-strings will use it

Notes

CPython impl
convert_field: None → value, 's' → str(value), 'r' → repr(value), 'a' → ascii(value), else ValueError('Unknown conversion specifier ' + conversion). format_field: return format(value, format_spec)
Order
get_field → convert_field → the spec is expanded (nested fields) → format_field
Conversions
The parser accepts any single character after !; only convert_field decides whether it is valid

FAQ

str.format cannot be extended, but string.Formatter can: subclass it, override convert_field, handle your character ('u') and return super().convert_field(value, conversion) for everything else. Then call YourFormatter().format(...).