30

神奇的 f-strings

 5 years ago
source link: http://www.dongwm.com/post/134/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在以前的文章我曾经说过,如果学习和在实际生产环境使用 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学习时, 就提过)。主要是当要格式化的参数很多时,可读性很差,还容易出错(数错占位符的数量),也不灵活,举个例子,name这个变量要在格式化时用2次,就要传入2次。学习手册>

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

是不是很酷?这个库的原理值得大家借鉴,我详细的分析下。

为什么根本没有importfuture_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不是唯一可能的格式化字符串选择,但它是最好最正确的那个选择!!!

延伸阅读


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK