为什么在生成器中 "del x" 不会改变结果,而 "x.append" 会呢?

4
我可以做到这一点:
>>> x = [2,3,4] 
>>> y = (v * 2 for v in x)
>>> del x                          # x is deleted
>>> print(list(y))                 # y still exists
[4, 6, 8]

这可能让我认为生成器y与列表x无关。但我也可以这样做:

>>> a = [2, 3, 4]
>>> b = (v * 2 for v in a)
>>> a.append(5)                   # change a
>>> print(list(b))                # b is also changed
[4, 6, 8, 10]

这让我感觉生成器b仍然指向列表a。因此,我想知道生成器是如何构建的。或者第一种情况中x被删除了的原因。

3
del x 并不一定会删除被 x 引用的对象,它只是删除了 *名称 x*。因此,生成器仍然引用着 a - juanpa.arrivillaga
那么内容仍然在内存中吗?那么内存实际上是何时被释放的呢? - ssd
1
当所有对该对象的引用都被删除后,内存将被释放。在你的情况下,在名称 x 被删除之后,生成器仍然保留对该对象的引用。 - inspectorG4dget
1
这取决于你的Python实现。CPython使用引用计数(实际上,它还使用垃圾回收来检测和释放引用循环)。当您的对象没有更多的引用时,内存将被释放。 - juanpa.arrivillaga
3个回答

6

del 命令并不会删除对象,它只是删除对应的名称。只要还有引用指向对象,它们就会存在。 变量名称 x 和生成器 y 都指向同一个对象(列表)。 如果您使用 del x 删除变量名称 x,则生成器仍然保留其引用。 如果您修改了 x,则生成器会看到这一修改,因为它也指向同一个对象。


4

生成器表达式基于惰性求值的概念工作。

生成器不会在内存中存储整个列表[4, 6, 8],而是存储(x * 2 for x in <some list>)的定义,并且仅在需要时计算下一个值。

在定义中存储的内容之一是所有用于计算表达式的源变量的引用。当在生成器表达式中使用x时,其引用将被存储,并在需要时进行解引用。

现在,执行

del x

只会减少与该值关联的引用计数器。在这两种情况下,都有两个引用(x以及生成器中的引用),直到删除其中一个为止。生成器引用仍然存在,因此可以评估它。


1
说“存储所有使用的源变量”是具有误导性的。实际上,只有目标表达式(在本例中为x*2)是惰性求值的---因为它是惰性求值的,所以它将反映对其中使用的变量的更改。只有迭代源(在这里是x)是急切地求值。例如,如果您执行类似于g = (a*b for a in x)的操作,则生成器中的值将反映生成器迭代时b的值,而不是生成器创建时b的值。 - BrenBarn
@BrenBarn也许更正确的说法是,源引用是被存储的?而迭代源则是急切地被评估的? - juanpa.arrivillaga
@BrenBarn 我认为你误解了那句话的含义,但我已经编辑过以便更清晰地表达。 - cs95

2

只要生成器仍然保持对x的引用,删除x就没有效果。这就像:

x = [2,3,4] 
y=x
del x
print(y)

除了引用不是由命名变量而是由生成器在内部保留。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接