ZetCode

Python __format__ 方法

最后修改于 2025 年 4 月 8 日

本综合指南探讨 Python 的 __format__ 方法,这是一个控制对象如何格式化为字符串的特殊方法。我们将介绍基本用法、格式规范、自定义格式和实际示例。

基本定义

__format__ 方法由内置的 format() 函数和字符串格式化操作调用。它根据格式规范返回对象的字符串表示形式。

主要特征:它接受格式规范字符串作为参数,返回格式化的字符串表示形式,并且可以被重写以自定义自定义对象的格式化行为。

基本的 __format__ 实现

这是一个简单的实现,展示了 __format__ 如何与基本格式规范一起使用。它演示了该方法的基本结构。

basic_format.py
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius
    
    def __format__(self, format_spec):
        if format_spec == 'f':
            return f"{self.celsius * 9/5 + 32:.2f}°F"
        elif format_spec == 'k':
            return f"{self.celsius + 273.15:.2f}K"
        else:
            return f"{self.celsius:.2f}°C"

temp = Temperature(25)
print(f"Default: {temp}")        # Default: 25.00°C
print(f"Fahrenheit: {temp:f}")   # Fahrenheit: 77.00°F
print(f"Kelvin: {temp:k}")       # Kelvin: 298.15K

此示例显示了一个 Temperature 类,该类根据格式规范以不同的方式进行格式化。__format__ 方法检查 format_spec 参数以确定输出格式。

该方法处理三种情况:默认的摄氏度显示、华氏度转换('f' 说明符)和开尔文转换('k' 说明符)。每种情况都返回一个正确格式化的字符串。

实现数值格式化

__format__ 方法可以实现类似于内置类型的数值格式化,支持对齐、填充和精度规范。

numeric_formatting.py
class FixedPoint:
    def __init__(self, value, scale=100):
        self.value = value
        self.scale = scale
    
    def __format__(self, format_spec):
        if not format_spec:
            return str(self.value / self.scale)
        
        # Handle format specifications like :.2f
        if format_spec.startswith('.'):
            precision = int(format_spec[1:-1])
            fmt = f"{{:.{precision}f}}"
            return fmt.format(self.value / self.scale)
        
        # Handle width and alignment
        parts = format_spec.split(',')
        num = self.value / self.scale
        if len(parts) == 1:
            return f"{num:{format_spec}}"
        else:
            width, align = parts
            return f"{num:{align}{width}}"

num = FixedPoint(31415)
print(f"Default: {num}")          # Default: 314.15
print(f"Formatted: {num:.2f}")    # Formatted: 314.15
print(f"Aligned: {num:10,.2f}")   # Aligned:     314.15

这个 FixedPoint 类存储具有固定小数位的值。 __format__ 方法解析格式规范,以提供类似于浮点数的灵活格式化选项。

该实现处理精度规范(如'.2f')、宽度和对齐方式。 它演示了如何解析和应用复杂的格式规范。

自定义格式规范语言

对于复杂的对象,您可以定义自定义格式规范语言,该语言控制字符串表示形式的不同方面。

custom_format_spec.py
class Person:
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height
    
    def __format__(self, format_spec):
        if not format_spec:
            return f"{self.name} (age: {self.age}, height: {self.height}cm)"
        
        parts = format_spec.split(':')
        if parts[0] == 'short':
            return self.name
        elif parts[0] == 'long':
            if len(parts) > 1 and parts[1] == 'metric':
                return (f"{self.name}, {self.age} years old, "
                       f"{self.height}cm tall")
            else:
                return (f"{self.name}, {self.age} years old, "
                       f"{self.height / 2.54:.1f} inches tall")
        else:
            return str(self)

person = Person("Alice", 30, 170)
print(f"Default: {person}")               # Default: Alice (age: 30, height: 170cm)
print(f"Short: {person:short}")           # Short: Alice
print(f"Long metric: {person:long:metric}") # Long metric: Alice, 30 years old, 170cm tall

此 Person 类实现了一种自定义格式规范语言,具有“short”和“long”格式,以及可选的单位规范。 __format__ 方法解析这些规范以产生不同的输出格式。

该示例展示了如何设计特定领域的格式语言,该语言使对象格式化对于特定用例更具表现力和实用性。

格式化复合对象

__format__ 方法可以将格式化委托给包含的对象,从而从组件部分创建复杂的格式化输出。

composite_formatting.py
class InventoryItem:
    def __init__(self, name, price, quantity):
        self.name = name
        self.price = price
        self.quantity = quantity
    
    def __format__(self, format_spec):
        if format_spec == 'csv':
            return f"{self.name},{self.price:.2f},{self.quantity}"
        elif format_spec == 'json':
            return (f'{{"name": "{self.name}", "price": {self.price:.2f}, '
                   f'"quantity": {self.quantity}}}')
        else:
            return (f"{self.name:20} ${self.price:>7.2f} "
                   f"x{self.quantity:03}")

class InventoryReport:
    def __init__(self, items):
        self.items = items
    
    def __format__(self, format_spec):
        header = "INVENTORY REPORT\n" + "="*40 + "\n"
        footer = "\n" + "="*40 + f"\nTotal items: {len(self.items)}"
        
        if format_spec == 'csv':
            body = "\n".join(f"{item:csv}" for item in self.items)
            return "name,price,quantity\n" + body
        elif format_spec == 'json':
            body = ",\n".join(f"{item:json}" for item in self.items)
            return f'{{"items": [\n{body}\n]}}'
        else:
            body = "\n".join(f"{item}" for item in self.items)
            return header + body + footer

items = [
    InventoryItem("Widget", 12.99, 42),
    InventoryItem("Gadget", 7.50, 13),
    InventoryItem("Thingy", 3.20, 89)
]
report = InventoryReport(items)
print(f"Default report:\n{report}")
print(f"\nCSV report:\n{report:csv}")
print(f"\nJSON report:\n{report:json}")

此示例显示了一个复合 InventoryReport 类,该类根据格式规范以不同的方式格式化其包含的 InventoryItem 对象。 每个类都实现自己的 __format__ 方法。

该报告可以用不同的输出格式(默认、CSV、JSON)进行格式化,每种格式规范都传递给包含的项目,以便在整个对象层次结构中保持一致的格式。

高级格式规范解析

为了获得最大的灵活性,__format__ 方法可以实现格式规范的复杂解析,类似于内置类型。

advanced_format_parsing.py
class ScientificNumber:
    def __init__(self, value):
        self.value = value
    
    def __format__(self, format_spec):
        if not format_spec:
            return str(self.value)
        
        # Parse format specification components
        fill = ' '
        align = '>'
        sign = '-'
        width = 0
        precision = 6
        type_char = 'g'
        
        # Simplified parsing - real implementation would be more robust
        if '>' in format_spec or '<' in format_spec or '^' in format_spec:
            align = format_spec[0]
            rest = format_spec[1:]
        else:
            rest = format_spec
        
        if '.' in rest:
            width_part, precision_part = rest.split('.')
            width = int(width_part) if width_part else 0
            precision = int(precision_part[:-1]) if precision_part[:-1] else 6
            type_char = precision_part[-1] if precision_part else 'g'
        else:
            width = int(rest[:-1]) if rest[:-1] else 0
            type_char = rest[-1] if rest else 'g'
        
        # Apply formatting based on parsed specifications
        if type_char == 'e':
            formatted = f"{self.value:.{precision}e}"
        elif type_char == 'f':
            formatted = f"{self.value:.{precision}f}"
        else:  # 'g' - general format
            formatted = f"{self.value:.{precision}g}"
        
        # Apply alignment and width
        if align == '>':
            return formatted.rjust(width, fill)
        elif align == '<':
            return formatted.ljust(width, fill)
        elif align == '^':
            return formatted.center(width, fill)
        elif align == '=':
            # Handle numeric padding between sign and digits
            if formatted[0] in '+-':
                return formatted[0] + formatted[1:].rjust(width-1, fill)
            return formatted.rjust(width, fill)
        else:
            return formatted

num = ScientificNumber(12345.6789)
print(f"Default: {num}")              # Default: 12345.6789
print(f"Scientific: {num:.3e}")       # Scientific: 1.235e+04
print(f"Fixed: {num:10.2f}")          # Fixed:   12345.68
print(f"Centered: {num:^15.4g}")      # Centered:    1.235e+04   

这个 ScientificNumber 类实现了类似于 Python 内置数值类型的高级格式规范解析。 它在单个格式字符串中处理对齐、精度和类型规范。

该示例演示了如何解析复杂的格式规范并将其应用于生成相同数值的不同字符串表示形式。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 我从 2007 年开始撰写编程文章。到目前为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程