Python format Method: Custom String Formatting

In Python, custom string representations are typically handled by __str__ and __repr__, but context-dependent and customized string formatting relies on the __format__ magic method. This article explains the role of __format__, how it interacts with Python's f-strings and the built-in format() function, how it differs from other string-conversion methods, and how to implement it in your own classes to parse custom format specifiers.

What is the __format__ Method?

The __format__ method is a special method in Python invoked whenever an object is evaluated within a formatting context. This includes calls to the built-in format(value, format_spec) function, the str.format() method, and formatted string literals (f-strings).

Its signature is:

def __format__(self, format_spec: str) -> str:
    ...

The method receives a single argument, format_spec, which is a string containing formatting instructions defined after the colon : in a format expression (for example, f"{value:format_spec}"). The method must return a string.

How __format__ Differs from __str__ and __repr__

If a class does not define __format__, Python falls back to object.__format__. If an empty format_spec is provided, object.__format__ calls __str__. However, if a non-empty format_spec is passed to an object that relies on default inheritance, Python raises a TypeError.

Implementing __format__

To implement custom formatting, evaluate the incoming format_spec string and return the appropriately styled output.

Example: Custom Currency Formatter

class Money:
    def __init__(self, amount: float):
        self.amount = amount

    def __format__(self, format_spec: str) -> str:
        if format_spec == "USD":
            return f"${self.amount:,.2f}"
        elif format_spec == "EUR":
            return f"€{self.amount:,.2f}"
        elif format_spec == "GBP":
            return f"£{self.amount:,.2f}"
        elif format_spec == "raw":
            return str(self.amount)
        
        # Fall back to standard float formatting if no currency match
        return format(self.amount, format_spec)

Usage:

price = Money(1250.5)

# Using f-strings
print(f"{price:USD}")   # Output: $1,250.50
print(f"{price:EUR}")   # Output: €1,250.50
print(f"{price:raw}")   # Output: 1250.5

# Delegating to standard numeric specifications
print(f"{price:.0f}")   # Output: 1250

# Using format() directly
print(format(price, "GBP"))  # Output: £1,250.50

Best Practices

  1. Handle Empty Format Specifications: Always support an empty string "" as format_spec. The standard convention is to delegate an empty specifier to __str__() or provide the natural default formatting.
  2. Delegate Standard Types: If your object wraps a standard type (like an int, float, or datetime), pass unrecognized specifiers directly to that attribute's format() function rather than raising an error immediately.
  3. Raise ValueError for Invalid Codes: If a user provides an unsupported or malformed format specifier, raise a ValueError with a descriptive message to match the behavior of built-in Python types.