如何正确格式化Unicode字符串?

时间:2017-05-03 09:33:30

标签: python-3.x unicode string-formatting

当我尝试使用str.format()的对齐功能在python中集中Unicode字符串时,我得到了错误的结果。例如,我想在字符串下划线然后想要居中。

underline_txt = ''.join(map(lambda x: x + '\u0332', 'AB'))
centered_txt = '{:^4}'.format(underline_txt)
print(centered_txt)

问题是centered_txt长4个字符,但打印输出是2个终端单元格宽。由于x + '\u0332'是一个终端单元格范围。

现在我的问题是:如何正确格式化Unicode字符串?

我可以通过手工填充字符串来解决问题,但我想知道是否有更通用的解决方案。 快速而肮脏的解决方案,如果使用len(underline_txt) == 0和使用其他组合字符时会出现问题,例如代字号(' \ u0303')。

str_len = len(underline_txt) / 4 
left_pad, right_pad = ' ' * math.floor(str_len), ' ' * math.ceil(str_len)
really_centered = left_pad + centered_txt + right_hand

1 个答案:

答案 0 :(得分:0)

我通过创建自己的string.Formatter找到了一个解决方案,我在其中覆盖了方法format_field(self, value, format_spec)。在format_field中,我检查给定的value是否是str的实例,并且它的打印长度与其字符长度不同。只有这样我才能进行" Unicode对齐"。解析format_specformat(value, format_spec)内置的python实现,它试图模仿其所有边缘情况。

import wcwidth

class UnicodeFormatter(string.Formatter):
    def format_field(self, value, format_spec):
        if not isinstance(value, str):
            # If `value` is not a string use format built-in
            return format(value, format_spec)
        if format_spec == '':
            # If `format_spec` is empty we just return the `value` string
            return value

        print_length = wcwidth.wcswidth(value)
        if len(value) == print_length:
            return format(value, format_spec)

        fill, align, width, format_spec = UnicodeFormatter.parse_align(format_spec)
        if width == 0:
            return value
        formatted_value = format(value, format_spec)
        pad_len = width - print_length
        if pad_len <= 0:
            return formatted_value
        left_pad = ''
        right_pad = ''
        if align in '<=':
            right_pad = fill * pad_len
        elif align == '>':
            left_pad = fill * pad_len
        elif align == '^':
            left_pad = fill * math.floor(pad_len/2)
            right_pad = fill * math.ceil(pad_len/2)
        return ''.join((left_pad, formatted_value, right_pad))

    @staticmethod
    def parse_align(format_spec):
        format_chars = '=<>^'
        align = '<'
        fill = None
        if format_spec[1] in format_chars:
            align = format_spec[1]
            fill = format_spec[0]
            format_spec = format_spec[2:]
        elif format_spec[0] in format_chars:
            align = format_spec[0]
            format_spec = format_spec[1:]

        if align == '=':
            raise ValueError("'=' alignment not allowed in string format specifier")
        if format_spec[0] in '+- ':
            raise ValueError('Sign not allowed in string format specifier')
        if format_spec[0] == '#':
            raise ValueError('Alternate form (#) not allowed in string format specifier')
        if format_spec[0] == '0':
            if fill is None:
                fill = '0'
            format_spec = format_spec[1:]
        if fill is None:
            fill = ' '
        width_str = ''.join(itertools.takewhile(str.isdigit, format_spec))
        width_len = len(width_str)
        format_spec = format_spec[width_len:]
        if width_len > 0:
            width = int(width_str)
        else:
            width = 0
        return fill, align, width, format_spec