神奇的 f-strings

如果学习和在实际生产环境使用 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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容