众所周知,Python编程语言的咒语之一就是,“有且只有一种方式达到目的。”(在你的Python交互命令行使用“import this”来查看完整列表。)但是,往往很多时候,你有无数的方式选择来达到目的。在这种情况下,我们无法明显看出哪种方式是最好的。
首先,让我们回忆一下Python为我们提供了这几种方法来连接字符串。我们可以用+运算符,例如:
>> "ABC" + "DEF"
"ABCDEF"
我们也可以用%运算符,它可以做的不只是连接,但仍然是一个合理的选项:
>>> “%S%S” %( "ABC", "DEF")
"ABCDEF"
正如我在以前的博客文章已经提到,我们也有一个更现代的方式做到这一点,那就是str.format方法:
>>> " ".format(X, Y)
"ABCDEF"
正如%操作符,str.format方法远比简单的连接更强大。但我想,这会让我深入理解相对速度。
现在,我们如何计时呢?在Jupyter(又名IPython中),我们可以使用神奇的“timeit”命令来运行代码。因此,我写了四大功能,其中每个都以不同的方式连接字符串。我故意使用全局变量(命名为“X”和“Y”),以包含原始字符串,和一个局部变量“Z”,来存放结果。结果随后由函数返回。(我们可以稍微放开一点“X”和“Y”的值和定义。)
我要指出,concat3和concat4几乎是相同的,因为它们都使用str.format方法。第一个使用参数的隐式位置,而第二个使用显式的位置。我决定,既然我已在做基准字符串连接测试,我不妨也看看当给出参数的索引时是否有任何速度差异。
然后我定义了两个全局变量:
X = "ABC"
Y = "DEF"
最后,我运行每个函数并计时:
%timeit concat1()
%timeit concat2()
%timeit concat3()
%timeit concat4()
结果如下:
concat1:153ns/loop
concat1:275ns/loop
concat1:398ns/loop
concat1:393ns/loop
从这个基准测试中,我们可以看到,concat1,它采用+,比任何其他的方法显著更快。这有点遗憾,因为我是多么爱用str.format - 但它也意味着,如果我做一大堆的字符串处理,我应该坚持+,这可能包含更少的功能,但远快于其他方式。
问题是,上述基准测试可能是有点问题的,因为我们使用短字符串。在Python中非常短的字符串是“被约束的”,这意味着它们被定义一次,然后保存在一个表中,这样他们不需要再次进行分配,并重新创建。毕竟,因为字符串是不可变的,为什么我们不止一次创建“ABC”呢?我们可以只引用创建的第一个“ABC”。
这可能会搞乱我们的基准测试。但是,试着检查更大的字符串也很好。幸运的是,我们使用全局变量 - 因此,通过改变这些全局变量的定义,我们可以运行我们的基准测试,并确保没有发生约束:
X = "ABC" * 10000
y = "DEF" * 10000
现在,当我们再次测试函数,这时我们得到:
concat1:2.64μs/loop
concat2:3.09μs/loop
concat3:3.33μs/loop
concat4:3.48μs/loop
每个循环所花的时间很多 - 但我们看到,我们的+操作符仍然是最快的。虽然差别没有那么巨大,但是依然是相当明显和显著。
那么如果我们不再使用全局变量,而是在函数中指定字符串呢?会有所不同吗?几乎可以肯定不是,但值得快速测试一下:
而我们最终的结果是:
concat1:4.89μs/loop
concat2:5.78μs/loop
concat3:6.22μs/loop
concat4:6.19μs/loop
再一次,我们看到,+操作符是最大赢家,但它的优势没有操作短字符串时那么明显了。 str.format方法明显缩短。我们可以看到,对于str.format方法来说,无论使用“ ”还是“{} {}”格式,基本上没有差异。
经过思考,这不足为奇。毕竟,+是一个非常简单的操作,而%和str.format做得更多。此外,str.format是一种方法,这意味着它将会有更大的开销。
现在,我也可以多跑几个测试 - 例如,有两个以上的字符串。但我认为,这表明至少在一定程度+是Python实现字符串连接的最快方式。此外,它表明我们可以快速,轻松且容易地做基准测试,进行各种实验,以便帮助我们理解什么是Python中的最佳实践。
领取专属 10元无门槛券
私享最新 技术干货