Python islice 的步长:只拿到两项,为什么原迭代器已经向前走了七步

前天 3阅读

从一条逐项生成的数据流里每隔三项取一次样本,islice 看起来像列表切片。结果列表只有两项,可是随后继续读取原流,却从第八项开始。被跳过的数据已经被读取并丢弃,源迭代器不会为另一个使用者保留这些位置。

下面用 seen 记录上游实际产出的数字。第一个实验完整耗尽切片,第二个只调用一次 next。把代码保存为 demo.py,运行 python demo.py;数据总共十项,没有外部输入或文件操作。

Python islice 的步长:只拿到两项,为什么原迭代器已经向前走了七步

AI生成概念插图:少量蓝色块被选出,灰色块也被游标经过;概念示意不表示真实运行界面。

from itertools import islice

def source(seen):
    for value in range(10):
        seen.append(value)
        yield value

seen = []
stream = source(seen)
chosen = list(islice(stream, 1, 7, 3))
assert chosen == [1, 4]
assert seen == list(range(7))
print("chosen:", chosen)
print("consumed:", seen)
assert next(stream) == 7
print("next source value:", 7)

partial_seen = []
partial_stream = source(partial_seen)
window = islice(partial_stream, 1, 7, 3)
first = next(window)
assert first == 1 and partial_seen == [0, 1]
print("partial:", first, partial_seen)
assert next(partial_stream) == 2

empty_seen = []
empty_stream = source(empty_seen)
assert list(islice(empty_stream, 4, 2)) == []
assert empty_seen == [0, 1, 2, 3]
print("empty output consumed:", empty_seen)

try:
    islice(range(5), -1, None)
except ValueError:
    print("negative start rejected")
else:
    raise AssertionError("negative start accepted")

产出数量不能代替读取数量

chosen 是 [1, 4],而 consumed 是从 0 到 6 的七项。start=1 表示跳过位置零,step=3 表示只交付位置一和四,stop=7 表示耗尽这一切片时推进到位置七之前。为了找到选中的项,上游必须经过中间元素;为了确认切片完成,还会消耗最后一个选中项之后、停止位置之前的项。

随后 next(stream) 得到 7。这里所有位置都相对于调用 islice 时的源迭代器当前位置,不是数据源永远不变的全局编号。如果源已经被别的代码读取三次,再使用 start=1,又会从那个当前位置向前跳一项。需要全局序号时,应显式随数据保存,不能事后从切片参数猜测。

提前停下与完整耗尽不同

第二个实验只取一次 next(window),因此上游只交出 0 和 1,并未提前走到 stop。islice 是惰性的,创建对象本身也不会把整个区间读完。调试时把它临时转成 list 会改变源的位置;这个看似无害的检查可能让后续流程失去原本尚未消费的项。

第三个实验更容易被忽略:start 大于 stop 时,结果为空,但完整耗尽这个切片仍消耗到 start。空列表并不说明上游没有执行。若读取动作包含计数、解析或昂贵计算,这些成本一样可能发生。对一次性数据流,必须同时验收输出和源位置。

islice 不支持负起点、负终点或反向步长,也不能像可随机访问的列表那样从末尾回看。想保留尾部若干项,需要另行设计有限缓冲;想多次查看一段固定数据,可以先在可接受的容量内保存它。不要把无界源直接变成列表来解决回看问题。

把同一个源和它的切片交给两段交替执行的代码,也会让两者相互影响。示例读取 partial_stream 的下一项只是展示共享位置,之后没有继续使用 window。真实程序最好明确唯一读取者,或者让分流机制承担保存职责,并把允许占用的内存写清楚。

资料核对日期:2026年10月2日。代码在本地 Python 3.12.14 实际运行并通过断言;结果只对应文中给定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。