Kye*_*Shi 13 python iterator list lazy-evaluation data-structures
假设我有一个iterator当从中消耗元素时执行一些副作用的操作,例如修改列表。如果我定义一个list l和call l.extend(iterator),是否保证在迭代器中的元素被消耗时,extend将元素l逐个推入一个元素,而不是保留在缓冲区中然后一次推入所有元素?
我在计算机上使用Python 3.7进行了快速测试,list.extend基于该测试似乎很懒。(请参见下面的代码。)这是否由规范保证,如果可以,则在规范中提到了什么?
(此外,请随时批评我,并说“这不是Python风格的,您这个傻瓜!”-尽管如果您也想回答我这个问题也回答了我,我将不胜感激。自己的好奇心。)
假设我定义了一个迭代器,该迭代器会在运行时推送到列表中:
l = []
def iterator(k):
for i in range(5):
print([j in k for j in range(5)])
yield i
l.extend(iterator(l))
Run Code Online (Sandbox Code Playgroud)
以下是非延迟(即缓冲)与延迟可能extend实现的示例:
def extend_nonlazy(l, iterator):
l += list(iterator)
def extend_lazy(l, iterator):
for i in iterator:
l.append(i)
Run Code Online (Sandbox Code Playgroud)
这是当我运行的两个已知实现时发生的情况extend。
非懒惰:
l = []
extend_nonlazy(l, iterator(l))
Run Code Online (Sandbox Code Playgroud)
# output
[False, False, False, False, False]
[False, False, False, False, False]
[False, False, False, False, False]
[False, False, False, False, False]
[False, False, False, False, False]
# l = [0, 1, 2, 3, 4]
Run Code Online (Sandbox Code Playgroud)
懒:
# output
[False, False, False, False, False]
[False, False, False, False, False]
[False, False, False, False, False]
[False, False, False, False, False]
[False, False, False, False, False]
# l = [0, 1, 2, 3, 4]
Run Code Online (Sandbox Code Playgroud)
[False, False, False, False, False]
[True, False, False, False, False]
[True, True, False, False, False]
[True, True, True, False, False]
[True, True, True, True, False]
Run Code Online (Sandbox Code Playgroud)
My own experimentation shows that native list.extend seems to work like the lazy version, but my question is: does the Python spec guarantee that?
我不认为这个问题是懒惰 VS 非偷懒,因为,无论是在片分配或列表extend,你需要迭代的所有元素,但这些元素消耗一次(正常情况下)。您提出的问题更为重要:这些操作是原子操作还是非原子操作?请参阅Wikipedia中“原子性”的一种定义:
原子性保证将每笔交易视为一个单独的“单位”,该单位要么完全成功,要么完全失败。
看一下这个例子(CPython 3.6.8):
>>> def new_iterator(): return (1/(i-2) for i in range(5))
>>> L = []
>>> L[:] = new_iterator()
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
>>> L
[]
Run Code Online (Sandbox Code Playgroud)
切片分配由于异常而失败(i == 2=> 1/(i - 2)引发异常),并且列表保持不变。因此,切片分配操作是atomic。
现在,使用以下示例extend:
>>> L.extend(new_iterator())
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
>>> L
[-0.5, -1.0]
Run Code Online (Sandbox Code Playgroud)
引发异常时,前两个元素已添加到列表中。该extend操作不是原子操作,因为失败不会使列表保持不变。
该extend操作是否应为原子操作?坦白地说,我对此一无所知,但正如@wim的答案所写,真正的问题是文档中并未明确说明(更糟糕的是,文档断言这extend等同于切片分配,在参考文献中是不正确的)实施)。
Python
list.extend(iterator)一定是懒惰的吗?
不。相反,有记录表明
l.extend(iterable)
Run Code Online (Sandbox Code Playgroud)
相当于
l[len(l):] = iterable
Run Code Online (Sandbox Code Playgroud)
在 CPython 中,这样的切片分配无论如何都会首先将右侧的生成器转换为列表(请参阅此处),即它iterable一次消耗所有内容。
严格来说,您问题中显示的示例与文档相矛盾。 我已经提交了一个文档错误,但很快就被 Raymond Hettinger 关闭了。
顺便说一句,有更简单的方法来证明这种差异。只需定义一个失败的生成器:
def gen():
yield 1
yield 2
yield 3
uh-oh
Run Code Online (Sandbox Code Playgroud)
现在L.extend(gen())会修改L,但L[:] = gen()不会。
| 归档时间: |
|
| 查看次数: |
266 次 |
| 最近记录: |