神奇的 f-strings
/ / / 阅读数:11348在以前的文章我曾经说过,如果学习和在实际生产环境使用 Python 3,版本至少应该从 Python3.6 开始,版本越高越好。现在是已经是 Python 3.7,按计划 Python 3.8 今年 10 月 20 日也会发布。
为什么我要强调从 Python 3.6 开始呢?我认为在这之前的版本无论是性能和语法完整性都不够,在 Python 3.6 时添加了异步生成器、异步推导语法,非常实用的 Path 模块等等,另外一个很重要的是添加了 f-strings 语法,大家用了就会发现根本停不下来。本文就和大家聊聊这个神奇的 f-strings。
f-string 是格式化字符串的新语法。与其他格式化方式相比,它们不仅更易读,更简洁,不易出错,而且速度更快!我们首先了解下可视化字符串语法的历史
1: %-formatting
最早的格式化是用 %(百分号), 它这么用:
In : name = 'Xiaoming' In : 'Hello %s' % name Out: 'Hello Xiaoming' |
% 符号前面使用一个字符串作为模板,模板中有标记格式的占位符号。占位符控制着显示的格式,这里用的%s
表示格式化成字符串,另外常用的是%d
(十进制整数)、%f
(浮点数)。
格式化语法也可以格式化多个变量,需要把变量用括号括起来:
In : id = 123
In : 'User[%s]: %s' % (id, name)
Out: 'User[123]: Xiaoming'
另外也支持使用字典的形式:
In : 'User[%(id)s]: %(name)s' % {'id': 123, 'name': 'Xiaoming'}
Out: 'User[123]: Xiaoming'
这种用法一直到现在仍然被使广泛使用,但是其实它是一种不被提倡使用的语法 (我初 Python 学习时,
2. str.format()
从 Python 2.6 开始,新增了一种格式化字符串的函数str.format()
,基本语法是通过{}
和:
来代替以前的%
。format 函数支持通过位置、关键字、对象属性和下标等多种方式使用,不仅参数可以不按顺序,也可以不用参数或者一个参数使用多次。并且可以通过对要转换为字符串的对象的__format __
方法进行扩展。
In : name = 'Xiaoming'
In : 'Hello {}'.format(name)
Out: 'Hello Xiaoming'
通过位置访问:
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'
通过关键字访问:
In : 'Hello {name}'.format(name='Xiaoming')
Out: 'Hello Xiaoming'
通过对象属性访问:
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'
通过下标访问:
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 会计算其中的用大括号包起来的表达式,并将计算后的值替换进去。
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 是三种方案中最快的:
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 版本上体验一下:
❯ 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
是不是很酷?这个库的原理值得大家借鉴,我详细的分析下。
为什么根本没有 import future_fstrings 代码却能正常运行?
这是因为在你安装 future_fstrings 这个包时,在 site-packages 下添加一个叫做aaaaa_future_fstrings.pth
的文件。pth 文件是路径配置文件,这种文件本来是为了扩充搜索模块路径进 sys.path 的,也有一些包用它做高级定制。
我们这里提aaaaa_future_fstrings.pth
在这里被用来导入包了。所以 CPython 解释器运行时会加载这个 pth 文件,然后调用其中的future_fstrings.register
方法。
emmm, pth 文件很多同学感觉很陌生,其他它离我们很近,只是作为开发者日常基本不需要关注它。举一个最常见的例子easy-install.pth
。有时候为了开发方便,安装包的时候是这样用的:
❯ git clone https://github.com/dongweiming/aiomcache
❯ cd aiomcache
❯ pip install -e .
这样你使用的 aiomcache,是你本地的版本:
❯ cd ~/test # 切换到其他目录,防止import本地目录下的aiomcache模块
❯ python -c 'import aiomcache; print(aiomcache)'
<module 'aiomcache' from '/Users/dongweiming/aiomcache/aiomcache/__init__.py'>
这样怎么生效的呢?有些同学会说「嗯,安装后就放入了 site-packages 下了呗」,其实不然,你去系统 Site 包目录下是找不到,但是呢,sys.path 是可以找到的:
❯ python -m site
sys.path = [
'/Users/dongweiming/test',
'/Users/dongweiming/.venvburrito/lib/python2.7/site-packages',
'/Users/dongweiming/vue-admin/venv/lib/python37.zip',
'/Users/dongweiming/vue-admin/venv/lib/python3.7',
'/Users/dongweiming/vue-admin/venv/lib/python3.7/lib-dynload',
'/usr/local/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7',
'/Users/dongweiming/vue-admin/venv/lib/python3.7/site-packages',
'/Users/dongweiming/aiomcache',
]
USER_BASE: '/Users/dongweiming/.local' (exists)
USER_SITE: '/Users/dongweiming/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: False
可以看到,搜索的目录包含了/Users/dongweiming/aiomcache
,所以能找到。这是怎么生效的呢?其实就是靠上面说的easy-install.pth
:
❯ cat /Users/dongweiming/vue-admin/venv/lib/python3.7/site-packages/easy-install.pth
/Users/dongweiming/aiomcache
看到了吧,所以说 pth 配置文件扩充了要搜索模块的路径。我们测试下把 aiomcache 这样删掉:
❯ sed -i '' '/aiomcache/d' /Users/dongweiming/vue-admin/venv/lib/python3.7/site-packages/easy-install.pth # Mac下Sed用法
再 import,和查看搜索目录就找不到 aiomcache 了:
❯ python -c 'import aiomcache; print(aiomcache)'
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'aiomcache'
❯ python -m site
... # 忽略输出,和上面的区别是没有包含aiomcache的哪一行
这个包实现 f-strings 的原理是?
奥妙就在# coding: future_fstrings
这一句中,另外一个写法是# -*- coding: future_fstrings -*-
。
想必大家会想到 Python 2 里面的# coding: utf-8
头了吧,这句注释用来声明文件编码。这个包巧妙的利用了它,在调用 register 函数时给 codecs 模块注册了新的文件编码方式,在词法扫描时把 f-strings 转换成 format 的写法,上面的例子等价于:
❯ future-fstrings-show test.py
# coding: future_fstrings
name = "Xiaoming"
print('Hello is {}.'.format((name)))
print('Hello {}'.format((name.upper())))
d = {'id': 123, 'name': 'Xiaoming'}
print('User[{}]: {}'.format((d["id"]), (d["name"])))
不过这样的实现性能肯定比原生的要差很多。大家有兴趣的可以尝个鲜儿~
结束语
在 Python 之禅中有这么一句:
There should be one-- and preferably only one --obvious way to do it.
也就是「应该提供一种,且最好只提供一种,一目了然的解决方案」。虽然 f-strings 不是唯一可能的格式化字符串选择,但它是最好最正确的那个选择!!!
想问一下 豆瓣图书的 api 被下掉了吗?