这样的代码经常发生:
l = []
while foo:
# baz
l.append(bar)
# qux
如果您要向列表中添加数千个元素,这将非常缓慢,因为列表必须不断调整大小以适应新元素。
在Java中,可以创建具有初始容量的ArrayList。如果你知道你的清单有多大,这将会更有效率。
我知道这样的代码通常可以被重构成一个列表理解式。但是,如果for/while循环非常复杂,这是不可行的。对于我们Python程序员来说,是否也有类似的方法?
这样的代码经常发生:
l = []
while foo:
# baz
l.append(bar)
# qux
如果您要向列表中添加数千个元素,这将非常缓慢,因为列表必须不断调整大小以适应新元素。
在Java中,可以创建具有初始容量的ArrayList。如果你知道你的清单有多大,这将会更有效率。
我知道这样的代码通常可以被重构成一个列表理解式。但是,如果for/while循环非常复杂,这是不可行的。对于我们Python程序员来说,是否也有类似的方法?
当前回答
根据我的理解,Python列表已经非常类似于数组列表。但如果你想调整这些参数,我在互联网上找到了这篇文章,可能会很有趣(基本上,只需要创建自己的ScalableList扩展):
http://mail.python.org/pipermail/python-list/2000-May/035082.html
其他回答
我运行了S.Lott的代码,通过预分配获得了同样10%的性能提升。我使用发电机尝试了Ned Batchelder的想法,并能够看到发电机的性能优于doAllocate。对于我的项目来说,10%的改进很重要,所以感谢每个人,因为这对我有帮助。
def doAppend(size=10000):
result = []
for i in range(size):
message = "some unique object %d" % ( i, )
result.append(message)
return result
def doAllocate(size=10000):
result = size*[None]
for i in range(size):
message = "some unique object %d" % ( i, )
result[i] = message
return result
def doGen(size=10000):
return list("some unique object %d" % ( i, ) for i in xrange(size))
size = 1000
@print_timing
def testAppend():
for i in xrange(size):
doAppend()
@print_timing
def testAlloc():
for i in xrange(size):
doAllocate()
@print_timing
def testGen():
for i in xrange(size):
doGen()
testAppend()
testAlloc()
testGen()
输出
testAppend took 14440.000ms
testAlloc took 13580.000ms
testGen took 13430.000ms
根据我的理解,Python列表已经非常类似于数组列表。但如果你想调整这些参数,我在互联网上找到了这篇文章,可能会很有趣(基本上,只需要创建自己的ScalableList扩展):
http://mail.python.org/pipermail/python-list/2000-May/035082.html
对于某些应用程序,字典可能是您正在寻找的。例如,在find_totient方法中,我发现使用字典更方便,因为我没有零索引。
def totient(n):
totient = 0
if n == 1:
totient = 1
else:
for i in range(1, n):
if math.gcd(i, n) == 1:
totient += 1
return totient
def find_totients(max):
totients = dict()
for i in range(1,max+1):
totients[i] = totient(i)
print('Totients:')
for i in range(1,max+1):
print(i,totients[i])
这个问题也可以用预分配的列表来解决:
def find_totients(max):
totients = None*(max+1)
for i in range(1,max+1):
totients[i] = totient(i)
print('Totients:')
for i in range(1,max+1):
print(i,totients[i])
我觉得这不是很优雅,而且容易产生错误,因为我存储的是None,如果我不小心错误地使用它们,它可能会抛出异常,而且因为我需要考虑映射让我避免的边缘情况。
没错,字典的效率不会那么高,但正如其他人评论的那样,速度上的微小差异并不总是值得冒重大维护风险。
Python的列表不支持预分配。Numpy允许您预分配内存,但在实践中,如果您的目标是加速程序,那么这样做似乎不值得。
该测试只是将一个整数写入列表,但在实际应用程序中,每次迭代都可能执行更复杂的操作,这进一步降低了内存分配的重要性。
import timeit
import numpy as np
def list_append(size=1_000_000):
result = []
for i in range(size):
result.append(i)
return result
def list_prealloc(size=1_000_000):
result = [None] * size
for i in range(size):
result[i] = i
return result
def numpy_prealloc(size=1_000_000):
result = np.empty(size, np.int32)
for i in range(size):
result[i] = i
return result
setup = 'from __main__ import list_append, list_prealloc, numpy_prealloc'
print(timeit.timeit('list_append()', setup=setup, number=10)) # 0.79
print(timeit.timeit('list_prealloc()', setup=setup, number=10)) # 0.62
print(timeit.timeit('numpy_prealloc()', setup=setup, number=10)) # 0.73
最快的方法-使用* like list1 = [False] * 1_000_000
比较所有常用方法(列表追加、预分配、for和while),我发现使用*可以获得最高效的执行时间。
import time
large_int = 10_000_000
start_time = time.time()
# Test 1: List comprehension
l1 = [False for _ in range(large_int)]
end_time_1 = time.time()
# Test 2: Using *
l2 = [False] * large_int
end_time_2 = time.time()
# Test 3: Using append with for loop & range
l3 = []
for _ in range(large_int):
l3.append(False)
end_time_3 = time.time()
# Test 4: Using append with while loop
l4, i = [], 0
while i < large_int:
l4.append(False)
i += 1
end_time_4 = time.time()
# Results
diff_1 = end_time_1 - start_time
diff_2 = end_time_2 - end_time_1
diff_3 = end_time_3 - end_time_2
diff_4 = end_time_4 - end_time_3
print(f"Test 1. {diff_1:.4f} seconds")
print(f"Test 2. {diff_2:.4f} seconds")
print(f"Test 3. {diff_3:.4f} seconds")
print(f"Test 4. {diff_4:.4f} seconds")
print("\nTest 2 is faster than - ")
print(f" Test 1 by - {(diff_1 / diff_2 * 100 - 1):,.0f}%")
print(f" Test 3 by - {(diff_3 / diff_2 * 100 - 1):,.0f}%")
print(f" Test 4 by - {(diff_4 / diff_2 * 100 - 1):,.0f}%")