关于python:为什么连接字符串比连接它们更快?

Why is concatenating strings running faster than joining them?

本问题已经有最佳答案,请猛点这里访问。

据我所知,"join(iterable_of_strings)是连接字符串的首选方法,因为它允许优化,避免将不可变对象重写到内存的次数超过必要次数。

在表达式中添加字符串的运行速度比在我的中等数量的操作中加入字符串要快。

我在joined上得到了大约2.9-3.2秒的时间,在我的笔记本电脑上用python 3.3运行这个代码得到了2.3-2.7秒的时间。我在谷歌上找不到一个好答案。有人能解释一下可能发生的事情吗?或者能指导我找到一个好的资源吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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()


As I understand it"".join(iterable_of_strings) is the preferred way to concatenate strings because it allows for optimizations that avoid having to rewrite the immutable object to memory more times than necessary.

你理解有点不正确。根据您解释的原因,"".join(iterable_of_strings)是连接字符串的iterable的首选方法。

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

除非有这么多的字符串,否则.join()不会变快,否则使用其他方法的代码会变得很愚蠢。当这种情况发生时,取决于您拥有什么样的Python实现、什么版本以及字符串的长度,但我们通常讨论的是10个以上的字符串。

虽然并非所有的实现都有快速的连接,但我已经测试了cpython、pypy和jython,并且所有的实现都有更快的连接,或者只对几个字符串进行了同样快的连接。

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

一些时间安排:http://slides.colliberty.com/djangoconeu-2013//step-40

视频说明:http://youtu.be/50oio9onmks?T=18立方米


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

如果您有一个可从字符串开始的iterable,而不是一个以字符串作为属性的对象,则情况会有所不同。然后您可以直接在iterable上调用join,而不需要为每个调用构建一个新的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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}{}
"
.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))))

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

所以,从已经存在的列表中加入总是最快的。如果单个项目较短,则与+连接更快,但对于长字符串,连接速度总是最慢。我怀疑concatconcat_from_list之间显示的差异来自于测试代码中函数调用中的列表解包。