【问题标题】:How does slice assignment handle a generator based on the same container?切片分配如何处理基于同一容器的生成器?
【发布时间】:2022-08-19 04:39:51
【问题描述】:

我在 3.8 的 REPL 中尝试了这段代码:

>>> a = list(range(10))
>>> a[:] = (i for i in a for _ in range(2))

我们根据来自生成器的元素分配a 的元素,并且该生成器正在迭代a,我们甚至没有元素的一一对应关系。这看起来很像modifying the list while iterating over it,所以我预计这会以某种方式表现不佳。

但相反,它完全按照天真的期望工作:

>>> a
[0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]

经过片刻的思考,Python 似乎必须在实际执行分配之前制作某种临时副本。毕竟,插入的切片可能与替换的切片大小不同(只要它不是扩展切片),这需要从切片之后移动元素;如果不评估生成器,就无法知道将它们移动多远。

然而,很容易想象一个仍然会遇到问题的实现。例如:将切片后的元素复制到一个临时的;从切片的开头开始标记为未使用;按照通常的.append 逻辑从生成器中附加元素;最后.extend 是临时的。 (当然,这对扩展切片不起作用,但扩展切片无论如何都不能调整列表的大小。)实现,我们的示例将立即命中IndexError,因为该列表将在生成器开始使用之前被清除。


那么:实际行为是否可靠/有保证?它是特定于版本的吗? Python 究竟是如何实现切片赋值的?

    标签: python list language-lawyer generator


    【解决方案1】:

    我很确定我至少可以验证一个临时列表(或元组,我想)生成器的内容完成,或者分配的结果在临时缓冲区中计算,然后交换回列表对象。

    观察如果我们用扩展切片尝试同样的事情会发生什么:

    >>> a[::2] = (0 for _ in range(10))
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: attempt to assign sequence of size 10 to extended slice of size 5
    >>> a
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    异常报告了将被分配的切片的长度,因此 Python 必须通过评估整个生成器来确定这一点。可以想象它从生成器中赋值,发现有多余的元素,然后消耗生成器来确定错误消息的总长度;但如果它以这种方式工作,那么a 将被修改,但事实并非如此。

    无论哪种方式,这都应该保证问题中观察到的行为。

    【讨论】:

      猜你喜欢
      • 2012-08-06
      • 2018-12-11
      • 2019-11-10
      • 2018-04-09
      • 2016-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多