Python3.6新的字符串格式化语法

f-string是格式化字符串的新语法。与其他格式化方式相比,它们不仅更易读,更简洁,不易出错,而且速度更快!我们首先了解下可视化字符串语法的历史

1: %-formatting

最早的格式化是用%(百分号), 它这么用:

1
2
3
4
In : name = 'Xiaoming'

In : 'Hello %s' % name
Out: 'Hello Xiaoming'

%符号前面使用一个字符串作为模板,模板中有标记格式的占位符号。占位符控制着显示的格式,这里用的%s表示格式化成字符串,另外常用的是%d(十进制整数)、%f(浮点数)。

格式化语法也可以格式化多个变量,需要把变量用括号括起来:

1
2
3
In : id = 123
In : 'User[%s]: %s' % (id, name)
Out: 'User[123]: Xiaoming'

另外也支持使用字典的形式:

1
2
In : 'User[%(id)s]: %(name)s' % {'id': 123, 'name': 'Xiaoming'}
Out: 'User[123]: Xiaoming'

这种用法一直到现在仍然被使广泛使用,但是其实它是一种不被提倡使用的语法(我初Python学习时,就提过)。主要是当要格式化的参数很多时,可读性很差,还容易出错(数错占位符的数量),也不灵活,举个例子,name这个变量要在格式化时用2次,就要传入2次。

2. str.format()

从 Python 2.6开始,新增了一种格式化字符串的函数str.format(),基本语法是通过{}:来代替以前的%。format函数支持通过位置、关键字、对象属性和下标等多种方式使用,不仅参数可以不按顺序,也可以不用参数或者一个参数使用多次。并且可以通过对要转换为字符串的对象的__format __方法进行扩展。

1
2
3
4
In : name = 'Xiaoming'

In : 'Hello {}'.format(name)
Out: 'Hello Xiaoming'

通过位置访问:

1
2
3
4
5
6
7
8
In : '{0}, {1}, {2}'.format('a', 'b', 'c')
Out: 'a, b, c'

In : '{2}, {1}, {0}'.format('a', 'b', 'c')
Out: 'c, b, a'

In : '{1}, {1}, {0}'.format('a', 'b', 'c')
Out: 'b, b, a'

通过关键字访问:

1
2
In : 'Hello {name}'.format(name='Xiaoming')
Out: 'Hello Xiaoming'

通过对象属性访问:

1
2
3
4
In : from collections import namedtuple
In : p = Point(11, y=22)
In : 'X: {0.x}; Y: {0.y}'.format(p)
Out: 'X: 11; Y: 22'

通过下标访问:

1
2
3
4
In : coord = (3, 5)

In : 'X: {0[0]}; Y: {0[1]}'.format(coord)
Out: 'X: 3; Y: 5

可以感受到format函数极大的扩展了格式化功能。但是当处理多个参数和更长的字符串时,str.format() 的内容仍然可能非常冗长,除了定义参数变量,需要把这些变量写进format方法里面。

3. f-Strings

现在好了,Python 3.6新增了f-strings,这个特性叫做字面量格式化字符串,F字符串是开头有一个f的字符串文字,Python会计算其中的用大括号包起来的表达式,并将计算后的值替换进去。

1
2
3
4
5
6
7
8
9
10
11
12
In : name = 'Xiaoming'

In : f'Hello {name}'
Out: 'Hello Xiaoming'

In : f'Hello {name.upper()}'
Out: 'Hello XIAOMING'

In : d = {'id': 123, 'name': 'Xiaoming'}

In : f'User[{d["id"]}]: {d["name"]}'
Out: 'User[123]: Xiaoming'

如果你学过Ruby,ES6,你会非常容易接受这样的语法。另外在速度上,f-strings是三种方案中最快的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
In : import timeit

In : timeit.timeit("""name = "Xiaoming"
...: 'Hello is %s.' % name""", number = 10000)
Out: 0.0023188740001387487

In : 'Hello is %s.' % name
Out: 'Hello is Xiaoming.'

In : timeit.timeit("""name = "Xiaoming"
...: 'Hello is {}.'.format(name)""", number = 10000)
Out: 0.0038487229999191186

In : timeit.timeit("""name = "Xiaoming"
...: f'Hello is {name}.'""", number = 10000)
Out: 0.0011758640002881293

可以侧面感受到,str.format最慢,%s的稍快一点,F-string是最快的!你还有什么利用不用它?

现在我写Python 3.6以上的代码时,我已经完全不用另外2种格式化用法了。

future-fstrings

通过上面的例子,希望我们有一个共识,就是如果你的项目或者工作中使用的Python版本已经不小于3.6,f-string格式化是首选方式,不仅在保持功能强大的同时语义上更容易理解,而且性能也有较大的提升。但是不巧你用不了Python的f-strings,还有个选择,就是 future-fstrings 这个项目。它的作者也是pre-commit作者,一个pytest和tox核心开发。

在我个人电脑的Python 2.7 版本上体验一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
❯ pip2.7 install future-fstrings
❯ cat test.py
# coding: future_fstrings

name = "Xiaoming"
print(f'Hello is {name}.')
print(f'Hello {name.upper()}')

d = {'id': 123, 'name': 'Xiaoming'}

print(f'User[{d["id"]}]: {d["name"]}')

~/sansa master*
❯ python -V
Python 2.7.15

~/sansa master*
❯ python test.py
Hello is Xiaoming.
Hello XIAOMING
User[123]: Xiaoming