python – 为什么range() – 函数比乘法项更慢以获取嵌套列表中的副本?

要复制现有列表中的嵌套列表,遗憾的是仅仅将其相乘是不够的,否则将创建引用而不是列表中的独立列表,请参阅此示例:

x = [[1, 2, 3]] * 2
x[0] is x[1]  # will evaluate to True

为了实现目标,您可以在列表推导中使用范围函数,例如,请参阅:

x = [[1, 2, 3] for _ in range(2)]
x[0] is x[1]  # will evaluate to False (wanted behaviour)

这是一种在不创建引用的情况下将列表中的项目相乘的好方法,并且在许多不同的网站上也对此进行了多次解释.

但是,有一种更有效的方法来复制列表元素.那个代码对我来说似乎有点快(通过命令行的timeit测量,并且对于下面的代码和上面代码中的范围(n),使用不同的参数n∈{1,50,100,10000}):

x = [[1, 2, 3] for _ in [0] * n]

但我想知道,为什么这段代码运行得更快?还有其他缺点(更多内存消耗或类似情况)?

python -m timeit '[[1, 2, 3] for _ in range(1)]'
1000000 loops, best of 3: 0.243 usec per loop

python -m timeit '[[1, 2, 3] for _ in range(50)]'
100000 loops, best of 3: 3.79 usec per loop

python -m timeit '[[1, 2, 3] for _ in range(100)]'
100000 loops, best of 3: 7.39 usec per loop

python -m timeit '[[1, 2, 3] for _ in range(10000)]'
1000 loops, best of 3: 940 usec per loop

python -m timeit '[[1, 2, 3] for _ in [0] * 1]'
1000000 loops, best of 3: 0.242 usec per loop

python -m timeit '[[1, 2, 3] for _ in [0] * 50]'
100000 loops, best of 3: 3.77 usec per loop

python -m timeit '[[1, 2, 3] for _ in [0] * 100]'
100000 loops, best of 3: 7.3 usec per loop

python -m timeit '[[1, 2, 3] for _ in [0] * 10000]'
1000 loops, best of 3: 927 usec per loop


# difference will be greater for larger n

python -m timeit '[[1, 2, 3] for _ in range(1000000)]'
10 loops, best of 3: 144 msec per loop

python -m timeit '[[1, 2, 3] for _ in [0] * 1000000]'
10 loops, best of 3: 126 msec per loop

解决方法:

这是对的;范围,即使在Python 3中,它产生一个紧凑的范围对象,在计算和存储之间的经典权衡中比列表更复杂.

由于列表变得太大而无法容纳缓存(如果我们关注性能的主要问题),范围对象会遇到另一个问题:当创建范围中的每个数字时,它会销毁并创建新的int对象(前256个左右的成本较低,因为它们是实习的,但它们之间的差异仍然会导致一些缓存未命中.该列表将继续引用相同的列表.

但是,仍然有更有效的选择;例如,bytearray将消耗比列表少得多的内存.可能最好的任务功能隐藏在itertools:repeat中.与范围对象一样,它不需要存储所有副本,但是像重复列表一样,它不需要创建不同的对象.因此,重复(无,x)中的_之类的东西只会戳到相同的几个缓存行(对象的迭代计数和引用计数).

最后,人们坚持使用范围的主要原因是因为它是突出显示的内容(在固定计数循环的惯用语和内置函数之间).

在其他Python实现中,范围很可能比重复更快;这是因为计数器本身已经保持了价值.我期待Cython或PyPy的这种行为.

上一篇:《Redis操作Python系列、setbit位修改|CSDN创作打卡》


下一篇:【Java】Java中函数传参是值传递