Python 为什么连接字符串比连接字符串运行得更快?

Python 为什么连接字符串比连接字符串运行得更快?,python,string,loops,memory,python-3.3,Python,String,Loops,Memory,Python 3.3,据我所知,“.join(字符串的iterable_)是连接字符串的首选方式,因为它允许进行优化,从而避免将不可变对象重写到内存的次数超过需要的次数 对于我来说,在表达式中添加字符串的运行速度确实要快于将它们连接起来进行中等数量的操作 在我的笔记本电脑上使用Python3.3运行此代码,joined和added的运行时间分别为2.9-3.2秒和2.3-2.7秒。我在谷歌上找不到一个好的答案。有人能解释一下可能发生的事情,或者告诉我一个好的资源吗 import uuid import time c

据我所知,“.join(字符串的iterable_)是连接字符串的首选方式,因为它允许进行优化,从而避免将不可变对象重写到内存的次数超过需要的次数

对于我来说,在表达式中添加字符串的运行速度确实要快于将它们连接起来进行中等数量的操作

在我的笔记本电脑上使用Python3.3运行此代码,joined和added的运行时间分别为2.9-3.2秒和2.3-2.7秒。我在谷歌上找不到一个好的答案。有人能解释一下可能发生的事情,或者告诉我一个好的资源吗

import uuid
import time

class mock:
    def __init__(self):
        self.name = "foo"
        self.address = "address"
        self.age = "age"
        self.primarykey = uuid.uuid4()

data_list = [mock() for x in range(2000000)]

def added():
    my_dict_list = {}
    t = time.time()
    new_dict = { item.primarykey: item.name + item.address + item.age for item in data_list }
    print(time.time() - t)

def joined():
    my_dict_list = {}
    t = time.time()
    new_dict = { item.primarykey: ''.join([item.name, item.address, item.age]) for item in data_list }
    print(time.time() - t)

joined()
added()

您看到的时差来自创建要传递给
join
的列表。虽然你可以通过使用元组得到一个小的加速,但它仍然比仅仅在只有几个短字符串的情况下用
+
串接要慢

如果一开始就有一组字符串,而不是一个属性为字符串的对象,那么情况就不同了。然后您可以直接在iterable上调用
join
,而无需为每个调用构建一个新的

下面是我对
timeit
模块所做的一些测试:

import timeit

short_strings = ["foo", "bar", "baz"]
long_strings = [s*1000 for s in short_strings]

def concat(a, b, c):
    return a + b + c

def concat_from_list(lst):
    return lst[0] + lst[1] + lst[2]

def join(a, b, c):
    return "".join([a, b, c])

def join_tuple(a, b, c):
    return "".join((a, b, c))

def join_from_list(lst):
    return "".join(lst)

def test():
    print("Short strings")
    print("{:20}{}".format("concat:",
                           timeit.timeit(lambda: concat(*short_strings))))
    print("{:20}{}".format("concat_from_list:",
                           timeit.timeit(lambda: concat_from_list(short_strings))))
    print("{:20}{}".format("join:",
                           timeit.timeit(lambda: join(*short_strings))))
    print("{:20}{}".format("join_tuple:",
                           timeit.timeit(lambda: join_tuple(*short_strings))))
    print("{:20}{}\n".format("join_from_list:",
                             timeit.timeit(lambda: join_from_list(short_strings))))
    print("Long Strings")
    print("{:20}{}".format("concat:",
                           timeit.timeit(lambda: concat(*long_strings))))
    print("{:20}{}".format("concat_from_list:",
                           timeit.timeit(lambda: concat_from_list(long_strings))))
    print("{:20}{}".format("join:",
                           timeit.timeit(lambda: join(*long_strings))))
    print("{:20}{}".format("join_tuple:",
                           timeit.timeit(lambda: join_tuple(*long_strings))))
    print("{:20}{}".format("join_from_list:",
                           timeit.timeit(lambda: join_from_list(long_strings))))
输出:

Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> ================================ RESTART ================================
>>> 
>>> test()
Short strings
concat:             0.5453461176251436
concat_from_list:   0.5185697357936024
join:               0.7099379456477868
join_tuple:         0.5900842397209949
join_from_list:     0.4177281794285359

Long Strings
concat:             2.002303591571888
concat_from_list:   1.8898819841869416
join:               1.5672863477837913
join_tuple:         1.4343144915087596
join_from_list:     1.231374639083505
因此,从现有列表加入总是最快的。如果单个项目较短,则使用
+
连接会更快,但对于长字符串,连接速度总是最慢。我怀疑
concat
concat\u from\u list
之间的差异来自于测试代码中函数调用中列表的解包

据我所知,“.join(字符串的iterable_)是连接字符串的首选方式,因为它允许进行优化,从而避免将不可变对象重写到内存的次数超过需要的次数

你理解有点错误
“”。join(iterable\u of_strings)
是连接字符串iterable的首选方法,原因如您所述

但是,您没有字符串的iterable。你只有三根弦。连接三个字符串的最快方法是将它们与
+
一起添加,或使用
.format()
%
。这是因为您必须首先创建iterable,然后连接字符串,所有这些都是为了避免复制一些非常小的字符串

.join()
的速度不会变快,除非你有太多的字符串,使得愚蠢的代码使用其他方法。发生这种情况的时间取决于您的Python实现、版本以及字符串的长度,但我们通常讨论的字符串不止十个

虽然并不是所有的实现都有快速连接,但我已经测试了CPython、PyPy和Jython,它们都有更快的连接速度,或者对于几个字符串都一样快

本质上,您应该根据代码的清晰度在
+
.join()
之间进行选择,直到代码运行为止。然后,如果您关心速度:配置文件并对代码进行基准测试。不要坐着猜测。

一些时间安排:


带视频说明:

cPython具有非常高效的就地字符串连接。如果我发现两个串联操作比具体化一个列表然后执行
'.join
,要快,这并不奇怪。事实上,concat\u-from\u-list比concat快,这表明测试代码中有错误。正如你所说的,可能是拆包。在任何情况下,这些计时中的很多时间都只是函数调用。我所做的测试表明,对于三个长字符串,
+
速度也更快,尽管我手头没有这方面的数据。很有趣,感谢您的全面回复。我肯定是在接受你对时间的使用,而不是我刚才用的那个更笨拙的成语。@TimWilder不,不要。如果你想让你的代码运行得更快,那就分析一下
timeit
如果使用正确,它是一个有用的工具,但正确使用并不容易,而且您没有用实际数据测试实际运行的代码,这意味着,一旦您发现代码速度较慢,最好能帮助您了解哪些类型的更改值得尝试。@Lennart我会记住这一点。我确信现在就去读一些关于Python的评测选项的总线读物是值得的。