克服Python中慢速列表创建问题
晚上好!
我在测试列表和迭代器创建的速度时,发现了时间差异非常惊人。看看下面的内容:
>>> 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倍。我大致明白为什么会这样,但我更关心的是解决方案。有没有人知道类似于列表的数据结构,可以快速创建?(基本上,我想知道有没有办法直接从迭代器(比如 map
或 filter
函数等)转换成列表,而不会有太大的性能损失)
为了速度,我可以牺牲以下几点:
添加、插入、弹出和删除元素。
切片操作。
反转列表或任何就地操作,比如排序。
包含(
in
)操作。连接和重复操作。
欢迎所有建议,谢谢!
编辑:确实是针对Python 3的。
3 个回答
在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'>
所以,你其实并不是在比较两个相似的东西。
接着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
为了进一步补充其他两位的回答:
你对迭代器有一些误解。你提到的“创建时间慢”,其实是因为你理解错了,所以才想找一个“更快的容器”。
要知道,在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