当我尝试使用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
答案 0 :(得分:0)
我通过创建自己的string.Formatter
找到了一个解决方案,我在其中覆盖了方法format_field(self, value, format_spec)
。在format_field中,我检查给定的value
是否是str
的实例,并且它的打印长度与其字符长度不同。只有这样我才能进行" Unicode对齐"。解析format_spec
是format(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