使用.format语法的可识别语言环境的字符串格式

问题描述

是否有一种(通用的)方法可以使用.format() {:}语法在Python中进行区域设置感知的字符串格式化?我知道locale.format_string(),但这只接受旧的%语法。 {:n}存在,但仅可替代{:d},不适用于其他格式。

下面是我目前的方法,我希望在大多数非平凡的情况下都会失败。

import locale
import string


class LocaleFormatter(string.Formatter):
    def format_field(self,value,format_spec):
        if format_spec[-1] not in 'eEfFgGdiouxXcrs':  # types copied from locale._percent_re
            return super().format_field(value,format_spec)
        grouping = ',' in format_spec or '_' in format_spec
        format_spec = '%' + format_spec.replace(',','').replace('_','')
        return locale.format_string(format_spec,grouping)


locale.setlocale(locale.LC_ALL,'')
fmt = LocaleFormatter()
fmt.format("Length: {:,.2f} mm,width: {:,.2f} mm",1234.56,7890)  # expected format is 1.234,56 for most locales

解决方法

您可以实现将浮点数转换为decimal并设置精度以及在需要时手动添加前导空格的方法:

import decimal
import locale
import re
import string

class LocaleFormatter(string.Formatter):
    def format_field(self,value,format_spec):
        if format_spec[-1] not in 'eEfFgGdiouxXcrs':  # types copied from locale._percent_re
            return super().format_field(value,format_spec)
        grouping = ',' in format_spec or '_' in format_spec
        prec_re = re.match(r',?(?P<spec>(?P<width>\d+)?(.(?P<precision>\d+))?)?[eEfFgGdiouxXcrs]',format_spec)
        if prec_re is not None and prec_re.group('spec') is not None:
            space_len = prec_re.group('width')
            after_dot = prec_re.group('precision')
            if after_dot is not None:
                pre_dot_value_len = len(str(int(value)))
                ctx = decimal.Context(prec=int(after_dot) + pre_dot_value_len)
                # prec turned out to be the length of the decimal repr,not precision
                value = ctx.create_decimal(value)
            if space_len is not None:
                after_dot = 0 if after_dot is None else int(after_dot)
                pre_dot = len(str(value))
                how_many = pre_dot - after_dot - 1  # -1 for the dot character
                if how_many > 0:
                    format_spec = how_many * ' ' + format_spec
        format_spec = '%' + format_spec.replace(',','').replace('_','')
        return locale.format_string(format_spec,grouping)

locale.setlocale(locale.LC_ALL,'DE-DE')
fmt = LocaleFormatter()
res = fmt.format("Length: {:,.2f} mm,width: {:,2f} mm",1234.567878,7890)  # expected format is 1.234,56 for most locales
print(res)

这将导致:

Length: 1.234,57 mm,width:         7.890,000000 mm

请注意,格式化后建议的正确值是未正确舍入的。上面的一个-是。