在遍历切片时避免创建新列表的 Pythonic 方法?
Pythonic way to avoid creation of a new list when iterating over a slice?
很常见的模式是遍历列表的一部分和search/select/process项:
for item in array[start:end]:
# do something
据我所知,这会从 array[start:end]
复制项目创建一个新列表,遍历它,然后临时列表被垃圾收集。这看起来像是在浪费 CPU 用法。
使用 itertools.islice 解决这个问题。
问题:为什么这没有被广泛使用,并且像被教导可变默认函数参数的危险等那样被教导?
更新:
正如评论中指出的那样,“itertools.islice
适用于可迭代对象,而不是列表。在到达第 n 个元素之前,它必须迭代很多次”。
我想 islice
不是一个很好的例子,但是 创建一个临时列表只是为了遍历它是一个存在的问题。不是吗?
你可以这样做:
for i in xrange(start, end):
item = array[i]
# do something with the item
是否有 pythonic 方法来迭代序列的一部分而不创建它的副本?。因为这个例子中的负索引是不支持的,而且不如for item in sequence
.
切片是 Pythonic 的原因,即使它创建了一个副本,是因为切片的成本在事物的宏伟计划中通常是微不足道的。如果您从 list
中切出 10,000 个项目,只是为了迭代它们并丢弃它们,其成本是:
- 为 10,000 个指针(40K - 80K 字节)分配 space
- 正在将指针从源复制到新的 space
- 增加每个指针的引用计数
- 使用这里的内容
- 递减每个指针的引用计数
- 释放 space
在 C 级别,分配几 KB 的 RAM 和 incrementing/decrementing 几千个整数相对于解释器本身的开销来说是微不足道的(请记住,无论您做什么,参考当您循环并将每个元素存储在命名变量中时,将发生计数更新);几乎任何你做的结果list
都会比创建和销毁它的成本更高,特别是如果切片很小(解释器开销任何你可以用超过切片成本的东西来替换它。
如果复制确实有问题,并且start
索引是非平凡的(所以islice
不能直接跳到start
点,所以它不是-可行,并且切片足够大,您有理由担心内存),我所知道的最 Pythonic 方法基本上就是您提供的示例循环:
for i in xrange(start, end):
item = array[i]
# do something with the item
如果那太慢了(重复索引在 CPython 参考解释器中涉及数量惊人的解释器开销),最 Pythonic 的替代方法是将索引推送到 C 层:
from future_builtins import map # Only needed on Python 2 to get Py3 generator based map
for item in map(array.__getitem__, xrange(start, end)): # range on Python 3
# do something with the item
这避免了临时的list
,换取更高的每个元素成本(因为索引无法完全避免,只是隐藏在C层,不涉及字节码执行)。虽然绝对不那么 Pythonic(map
通常不被接受,并且显式引用像 __getitem__
这样的特殊方法是非常丑陋的)。
很常见的模式是遍历列表的一部分和search/select/process项:
for item in array[start:end]:
# do something
据我所知,这会从 array[start:end]
复制项目创建一个新列表,遍历它,然后临时列表被垃圾收集。这看起来像是在浪费 CPU 用法。
使用 itertools.islice 解决这个问题。
问题:为什么这没有被广泛使用,并且像被教导可变默认函数参数的危险等那样被教导?
更新:
正如评论中指出的那样,“itertools.islice
适用于可迭代对象,而不是列表。在到达第 n 个元素之前,它必须迭代很多次”。
我想 islice
不是一个很好的例子,但是 创建一个临时列表只是为了遍历它是一个存在的问题。不是吗?
你可以这样做:
for i in xrange(start, end):
item = array[i]
# do something with the item
是否有 pythonic 方法来迭代序列的一部分而不创建它的副本?。因为这个例子中的负索引是不支持的,而且不如for item in sequence
.
切片是 Pythonic 的原因,即使它创建了一个副本,是因为切片的成本在事物的宏伟计划中通常是微不足道的。如果您从 list
中切出 10,000 个项目,只是为了迭代它们并丢弃它们,其成本是:
- 为 10,000 个指针(40K - 80K 字节)分配 space
- 正在将指针从源复制到新的 space
- 增加每个指针的引用计数
- 使用这里的内容
- 递减每个指针的引用计数
- 释放 space
在 C 级别,分配几 KB 的 RAM 和 incrementing/decrementing 几千个整数相对于解释器本身的开销来说是微不足道的(请记住,无论您做什么,参考当您循环并将每个元素存储在命名变量中时,将发生计数更新);几乎任何你做的结果list
都会比创建和销毁它的成本更高,特别是如果切片很小(解释器开销任何你可以用超过切片成本的东西来替换它。
如果复制确实有问题,并且start
索引是非平凡的(所以islice
不能直接跳到start
点,所以它不是-可行,并且切片足够大,您有理由担心内存),我所知道的最 Pythonic 方法基本上就是您提供的示例循环:
for i in xrange(start, end):
item = array[i]
# do something with the item
如果那太慢了(重复索引在 CPython 参考解释器中涉及数量惊人的解释器开销),最 Pythonic 的替代方法是将索引推送到 C 层:
from future_builtins import map # Only needed on Python 2 to get Py3 generator based map
for item in map(array.__getitem__, xrange(start, end)): # range on Python 3
# do something with the item
这避免了临时的list
,换取更高的每个元素成本(因为索引无法完全避免,只是隐藏在C层,不涉及字节码执行)。虽然绝对不那么 Pythonic(map
通常不被接受,并且显式引用像 __getitem__
这样的特殊方法是非常丑陋的)。