克服Python中慢速列表创建问题

0 投票
3 回答
1744 浏览
提问于 2025-04-18 02:27

晚上好!

我在测试列表和迭代器创建的速度时,发现了时间差异非常惊人。看看下面的内容:

>>> timeit.timeit('map(lambda x: x**3, [1, 2, 3, 4, 5])')
0.4515998857965542
>>> timeit.timeit('list(map(lambda x: x**3, [1, 2, 3, 4, 5]))')
2.868906182460819

第一个测试中返回的迭代器版本运行速度比转换成列表快了超过6倍。我大致明白为什么会这样,但我更关心的是解决方案。有没有人知道类似于列表的数据结构,可以快速创建?(基本上,我想知道有没有办法直接从迭代器(比如 mapfilter 函数等)转换成列表,而不会有太大的性能损失)

为了速度,我可以牺牲以下几点:

  1. 添加、插入、弹出和删除元素。

  2. 切片操作。

  3. 反转列表或任何就地操作,比如排序。

  4. 包含(in)操作。

  5. 连接和重复操作。

欢迎所有建议,谢谢!

编辑:确实是针对Python 3的。

3 个回答

4

在Python 3.x中,map这个功能并不会直接生成一个列表,而只是生成一个迭代器,这和Python 2.x是不同的。

print(type(map(lambda x: x**3, [1, 2, 3, 4, 5])))
# <class 'map'>

如果你想要真正得到一个列表,可以用list这个函数来转换,像这样做

print(type(list(map(lambda x: x**3, [1, 2, 3, 4, 5]))))
# <class 'list'>

所以,你其实并不是在比较两个相似的东西。

4

接着thefourtheye的回答说,map函数里面的表达式在你开始遍历它之前是不会被计算的。这个例子应该很清楚:

from time import sleep

def badass_heavy_function():
    sleep(3600)

# Method call isn't evaluated
foo = map(lambda x: x(), [badass_heavy_function, badass_heavy_function])

# Methods call will be evaluated, please wait 2 hours
bar = list(map(lambda x: x(), [badass_heavy_function, badass_heavy_function]))

for _ in foo:
    # Please wait one hour
    pass
2

为了进一步补充其他两位的回答:

你对迭代器有一些误解。你提到的“创建时间慢”,其实是因为你理解错了,所以才想找一个“更快的容器”。

要知道,在Python中,创建一个列表对象是很快的:

%timeit list(range(10000))
10000 loops, best of 3: 164 µs per loop

你觉得慢的其实是你需要计算的循环,这个循环是用来生成要放入列表中的值的。

这里有一个非常不优化的例子,展示了如何慢慢“创建”一个新列表,内容来源于另一个列表:

x = list(range(10000))

def slow_loop(x):
    new = []
    for i in x:
        new.append(i**2)
    return new
%timeit slow_loop(x)
100 loops, best of 3: 4.17 ms per loop

实际上,花费时间的部分是这个循环,而这个循环在Python中是“慢”的。

如果你进行比较,技术上你实际上是在做这样的事情:

def your_loop(x):
    return list(map(lambda y: y**2, x))

%timeit your_loop(x)
100 loops, best of 3: 4.5 ms per loop

不过,有一种方法可以加快这个过程:

def faster_loop(x):
    return [i**2 for i in x]


%timeit faster_loop(x)
100 loops, best of 3: 3.67 ms per loop

虽然对于这种函数来说,提升并不大。关键是:慢的部分是数学运算,而不是列表或容器。你可以通过使用numpy来证明这一点。

arr = np.array(x)

%timeit arr ** 2
100000 loops, best of 3: 7.44 µs per loop

哇……速度提升太惊人了。

在进行基准测试时,我也经常犯这样的错误——人们总是怀疑系统,但对自己却不够怀疑。所以并不是说Python非常不优化或“慢”,而是你可能做错了。不要怀疑Python列表的效率,要怀疑你那慢慢的、低效的代码。这样你可能会更快找到正确的方法……

在这里,纯Python的**运算符非常慢,而简单的乘法运算要快得多:

def faster_loop2(x):
    return [i * i for i in x]

%timeit faster_loop2(x)
1000 loops, best of 3: 534 µs per loop

撰写回答