使用IPython %timeit,'{0}'.format()比str()和'{}'.format()更快,否则使用纯Python。

14

所以这是CPython的问题,不确定其他实现是否具有相同的行为。

但是,'{0}'.format()str()'{}'.format()更快。我发布了来自Python 3.5.2的结果,但我尝试过Python 2.7.12,趋势是相同的。

%timeit q=['{0}'.format(i) for i in range(100, 100000, 100)]
%timeit q=[str(i) for i in range(100, 100000, 100)]
%timeit q=['{}'.format(i) for i in range(100, 100000, 100)]

1000 loops, best of 3: 231 µs per loop
1000 loops, best of 3: 298 µs per loop
1000 loops, best of 3: 434 µs per loop

文档中查看object.__str__(self)

str(object)和内置函数format()print()调用,用于计算对象的“非正式”或可打印字符串表示。

所以,str()format()都调用同一个方法object.__str__(self),但速度上的差异从何而来?

更新 如@StefanPochmann和@Leon在评论中指出,它们获得了不同的结果。我尝试使用python -m timeit "..."运行它们,他们是正确的,因为结果如下:

$ python3 -m timeit "['{0}'.format(i) for i in range(100, 100000, 100)]"
1000 loops, best of 3: 441 usec per loop

$ python3 -m timeit "[str(i) for i in range(100, 100000, 100)]"
1000 loops, best of 3: 297 usec per loop

$ python3 -m timeit "['{}'.format(i) for i in range(100, 100000, 100)]"
1000 loops, best of 3: 420 usec per loop

看起来IPython正在做一些奇怪的事情...

新问题: 通过速度,将对象转换为str的首选方式是什么?


为了更快,您也可以尝试直接使用 i.__str__()。虽然 str 才是正确的方式。难道它对您来说还不够快吗? - Stefan Pochmann
1个回答

7
IPython的时间计算有些问题(尽管在不同的单元格中使用更长的格式字符串进行测试时,它的表现略有改善)。也许在同一单元格中执行不正确,我不太清楚。
无论如何,"{}"比"{pos}"快一点,后者比"{name}"快,但它们都比str慢。 str(val)是将对象转换为str的最快方法;它直接调用对象的__str__(如果存在),并返回生成的字符串。其他方法,如format(或str.format),由于需要额外的函数调用(调用format本身)以及处理任何参数、解析格式字符串和调用其args__str__,因此包含额外的开销。
对于str.format方法,"{}"使用自动编号;来自格式语法文档的一个小节:

从3.1版本开始,位置参数说明符可以省略,因此'{} {}'等同于'{0} {1}'

也就是说,如果您提供的字符串形式为:

"{}{}{}".format(1, 2, 3)

CPython立即知道这等价于:

"{0}{1}{2}".format(1, 2, 3)

使用包含数字位置的格式字符串;CPython 无法假定严格递增的数字(从0开始),必须按顺序解析每个括号才能正确处理,这会稍微减慢速度。
"{1}{2}{0}".format(1, 2, 3)

这就是为什么不允许将这两个混合在一起的原因:
"{1}{}{2}".format(1, 2, 3)

当您尝试这样做时,您将会得到一个很好的ValueError

ValueError: cannot switch from automatic field numbering to manual field specification

它还使用PySequence_GetItem获取这些位置参数, 我相信至少与PyObject_GetItem相比是快速的。

对于"{name}"值,CPython总是需要额外的工作,因为我们处理的是关键字参数而不是位置参数;这包括构建调用的字典和生成更多的LOAD字节码指令以加载key和值等。函数调用的关键字形式总是会引入一些开销。此外,似乎实际使用了PyObject_GetItem进行抓取,由于其通用性,会产生一些额外的开销。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接