Python格式化字符串的4种方式

59 阅读4分钟

在Python中格式化字符串最常用的方法是使用%操作符。预定义的文本模板在格式字符串中运算符的左侧。要插入的值作为单个值或多个值的元组提供在格式运算符的右侧。

a = 0b10111011 
b = 0xc5f 
print('Binary is %d, hex is %d' % (a, b)) 
>>> Binary is 187, hex is 316

格式字符串使用格式说明符(如%d)作为占位符,将被格式表达式右侧的值替换。格式说明符的语法来自C的printf函数,这是Python继承的。Python支持您所期望的所有常规选项printf,例如%s、%x和%f格式说明符,以及对小数点、padding、fill和对齐。

Python中的C格式字符串有四个问题。

第一个问题是,如果更改格式化表达式右侧元组中数据值的类型或顺序,可能会由于类型转换不兼容而出错。

key = 'my_var' 
value = 1.234 
formatted = '%-10s = %.2f' % (key, value) 
print(formatted) 
>>> my_var = 1.23

如果交互key和value,将会得到一个运行时异常

reordered_tuple = '%-10s = %.2f' % (value, key) 
>>> Traceback ...
TypeError: must be real number, not str
reordered_string = '%.2f = %-10s' % (key, value) 
>>> Traceback ... 
TypeError: must be real number, not str

第二个问题是在值格式化为字符串之前,你需要对其进行小的修改时,就会变得难以阅读。这是一个很常见的需求。

pantry = [ ('avocados', 1.25), ('bananas', 2.5), ('cherries', 15), ] 
for i, (item, count) in enumerate(pantry): 
    print('#%d: %-10s = %.2f' % (i, item, count)) 
    
>>> 
#0: avocados = 1.25 
#1: bananas = 2.50 
#2: cherries = 15.00

现在,我对正在格式化的值进行了一些修改使打印的信息更有用。这会导致元组在格式表达式太长,需要拆分 跨多行,这会影响可读性:

for i, (item, count) in enumerate(pantry): 
    print('#%d: %-10s = %d' % ( 
    i + 1, 
    item.title(),
    round(count))) 
    
>>>
#1: Avocados = 1 
#2: Bananas = 2 
#3: Cherries = 15

格式化表达式的第三个问题是要在格式字符串中多次使用相同的值,必须在右边的元组中重复它

template = '%s loves food. See %s cook.' 
name = 'Max' 
formatted = template % (name, name) 
print(formatted) 

>>> 
Max loves food. See Max cook.

为了帮助解决其中一些问题,Python中的%运算符还可以使用字典进行格式化。

解决问题1

key = 'my_var' 
value = 1.234
old_way = '%-10s = %.2f' % (key, value) 
new_way = '%(key)-10s = %(value).2f' % { 'key': key, 'value': value} # Original 
reordered = '%(key)-10s = %(value).2f' % { 'value': value, 'key': key} # Swapped 
assert old_way == new_way == reordered

解决问题3

name = 'Max' 
template = '%s loves food. See %s cook.' 
before = template % (name, name) # Tuple 
template = '%(name)s loves food. See %(name)s cook.' 
after = template % {'name': name} # Dictionary 
assert before == after

然而,字典格式字符串引入并加剧了其他问题。对于上面的问题#2,关于值的小修改在格式化它们之前,格式化表达式会变得越来越长由于存在字典键和右边的冒号操作符。

for i, (item, count) in enumerate(pantry): 
    before = '#%d: %-10s = %d' % ( 
        i + 1, 
        item.title(), 
        round(count)) 
    after = '#%(loop)d: %(item)-10s = %(count)d' % {
        'loop': i + 1, 
        'item': item.title(), 
        'count': round(count), 
        } 
        
   assert before == after

问题4:在格式化表达式时使用字典也会增加冗长 每个键必须至少指定两次,一次在格式规范中指定,一次在字典中指定为键,另一次可能在包含字典值的变量名

soup = 'lentil' 
formatted = 'Today\'s soup is %(soup)s.' % {'soup': soup} print(formatted) 

>>> 
Today's soup is lentil.

The format Built-in and str.format

Python3增加了对高级字符串格式的支持,比使用%运算符的旧C样式格式字符串更具表现力。 对于单个Python值,可以访问这个新功能,通过内置的格式函数

a = 1234.5678 
formatted = format(a, ',.2f') 
print(formatted) 

b = 'my string' 
formatted = format(b, '^20s') 
print('*', formatted, '*') 

>>> 
1,234.57 
* my string *

可以使用此功能将多个值一起格式化,通过调用str类型的新格式方法

key = 'my_var' 
value = 1.234 
formatted = '{} = {}'.format(key, value) 
print(formatted) 

>>> 
my_var = 1.234
formatted = '{:<10} = {:.2f}'.format(key, value) 
print(formatted)

>>> 
my_var = 1.23

在大括号中,还可以指定一个位置索引传递给格式方法以用于替换占位符的参数

formatted = '{1} = {0}'.format(key, value) 
print(formatted) 

>>> 
1.234 = my_var
formatted = '{0} loves food. See {0} cook.'.format(name) 
print(formatted) 

>>> 
Max loves food. See Max cook.

并未解决问题2

for i, (item, count) in enumerate(pantry): 
    old_style = '#%d: %-10s = %d' % ( 
        i + 1, 
        item.title(), 
        round(count)) 
        
    new_style = '#{}: {:<10s} = {}'.format( 
        i + 1, 
        item.title(), 
        round(count)) 
        
    assert old_style == new_style

插值格式字符串 f-string

key = 'my_var' 
value = 1.234 
formatted = f'{key} = {value}' 
print(formatted) 

>>> 
my_var = 1.234
formatted = f'{key!r:<10} = {value:.2f}' 
print(formatted) 

>>> 
'my_var' = 1.23
f_string = f'{key:<10} = {value:.2f}' 
c_tuple = '%-10s = %.2f' % (key, value) 
str_args = '{:<10} = {:.2f}'.format(key, value) 
str_kw = '{key:<10} = {value:.2f}'.format(key=key, value=value) 
c_dict = '%(key)-10s = %(value).2f' % {'key': key, 'value': value} 

assert c_tuple == c_dict == f_string 
assert str_args == str_kw == f_string
for i, (item, count) in enumerate(pantry): 
    old_style = '#%d: %-10s = %d' % ( 
        i + 1, 
        item.title(), 
        round(count)) 
        
    new_style = '#{}: {:<10s} = {}'.format( 
        i + 1, 
        item.title(), 
        round(count)) 
        
    f_string = f'#{i+1}: {item.title():<10s} = {round(count)}' 
    
    assert old_style == new_style == f_string