使用枚举的Python奇怪行为

使用枚举的Python奇怪行为,python,list,loops,iterator,enumerate,Python,List,Loops,Iterator,Enumerate,我知道我不应该在循环中修改列表,但出于好奇,我想知道为什么下面两个例子中的迭代次数不同 例1: x = [1, 2, 3, 4, 5] for i, s in enumerate(x): del x[0] print(i, s, x) 例2: x = [1,2,3,4,5] for i, s in enumerate(x): x = [1] print(i, s, x) 示例1仅运行3次,因为当i==3时,len(x)==2 示例2运行5次,即使len(x)=

我知道我不应该在循环中修改列表,但出于好奇,我想知道为什么下面两个例子中的迭代次数不同

例1:

x = [1, 2, 3, 4, 5]
for i, s in enumerate(x):
    del x[0]
    print(i, s, x)
例2:

x = [1,2,3,4,5]
for i, s in enumerate(x):
    x = [1]
    print(i, s, x)
示例1仅运行3次,因为当
i==3
时,
len(x)==2

示例2运行5次,即使
len(x)==1


所以我的问题是,
枚举
是否在循环开始时生成一个完整的
(索引,值)
对列表并在其中迭代?或者它们是在循环的每次迭代中生成的?

在第一个示例中,您实际上是在修改正在迭代的列表

另一方面,在第二种情况下,您只将一个新对象分配给名称
x
。不过,循环迭代的对象不会改变

查看Python中有关名称和变量的更详细说明。

返回迭代器或其他支持迭代的对象。由返回的迭代器的方法返回一个元组,该元组包含一个计数(起始值默认为0)和通过迭代iterable获得的值

返回容器中的下一项。如果没有其他项目,则引发
StopIteration
异常

enumerate()是否在循环开始时生成(索引、值)对的完整列表并对其进行迭代?或者它们是在循环的每次迭代中生成的

因此,
enumerate()
返回一个迭代器,在每次迭代时,
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu()
检查是否还有其他项<代码>枚举()不会在循环开始时创建完整列表


如上所述,在第二种情况下,您将为名称
x
指定一个新对象。循环所迭代的对象在迭代过程中不会改变。

只是对Wasi Ahmad和Wispervind所说内容的澄清。两者都表示“您只是将一个新对象指定给名称x”。这可能会让人有点困惑,因为它可能会被解释为“您正在创建一个新对象(
[1]
),并将其存储到名称
x
,您会说“是啊,为什么它没有改变?!”?!“要查看发生了什么,请打印出对象的id

x = [1, 2, 3, 4, 5]
y = x  # To keep a reference to the original list
print id(x), id(y)
for i, v in enumerate(x):
    x = [1]
    print id(x), id(y)
print id(x), id(y)


# output (somewhat contrived as I don't have a python environment set up)
#    X ID            Y ID
10000000000001 10000000000001
10000000000002 10000000000001
10000000000003 10000000000001
10000000000004 10000000000001
10000000000005 10000000000001
10000000000006 10000000000001
10000000000006 10000000000001
您会注意到,
x
id
在循环中每次都在变化,当您完成循环时,
x
将指向循环中的最后一次修改。当您完成循环时,它将迭代x的原始实例,而不管您是否仍然可以引用它。


如您所见,
y
指向原始的
x
。当您在循环中进行迭代时,即使
x
正在更改,
y
仍然指向原始的
x
,该原始的
x
仍在循环中。

确实:您的第一个片段修改了迭代列表;第二点将变量
x
添加到一个新列表中,使列表保持未修改状态,并被
enumerate()
横切。您可以通过访问www.pythontutor.com上的以下链接查看此操作,该链接允许您单步浏览代码并可视化变量的内容:

  • x
    已就地修改)

  • x
    被重定向到
    [1]

为了更好地了解发生了什么,请转到以下扩展代码:

x = [1,2,3,4,5]
view = enumerate(x)
for i, s in view:
    x = [1]
    print(i, s, x)

其他人已经指出,您的第二个示例只会更改
x
指向的值,而不会更改您正在迭代的列表。这是普通赋值(
x=[1]
)和(
x[:]=[1]
)之间差异的完美示例。后者修改列表
x
指向的位置:

将打印

(0, 1, [1])
列表
[1,2,3,4,5]
x

for i, s in enumerate(x):
enumerate()附加另一个标记,因此
[1,2,3,4,5]
现在被标记为
x
y
。enumerate()将继续使用
y
标记,而不是
x
标记

del x[0]
存储在内存中的列表被修改,因此
x
y
现在都引用
[2,3,4,5]

或者,当您使用

x = [1]
在内存中创建了一个新列表
[1]
,现在
x
标记指向该列表。
y
标记仍然指向原始列表

Python变量如何工作:

我不能假装我知道的足够多来回答你的问题,但我猜为什么它在这里的行为不同。在第一种情况下,你从同一个列表中删除,所以它在之前停止迭代是有意义的。在第二种情况下,你重新分配它。因此python可能认为它是一个
不同的
变量,并保留g使用
original
值x进行操作。完全没有枚举的情况下也会发生同样的情况!for循环不会重新计算迭代器,因此即使在for循环中重新分配
x
,循环仍将使用旧值。显然,如果从列表中删除元素,循环将以较少的迭代器完成ons。这一定是一个重复。enumerate需要用于未绑定的序列,因此不可能提前生成对。如果需要,只需使用
list(enumerate(…)
@jpmc26确实有一种我昨天没有找到的语言。它确实很好地解释了根本问题,但不是在循环的上下文中。是否应该标记它?我对这一点相当陌生,也不确定。Python是一种非常基于引用的语言。你不给名称x赋值,而是给名称x赋值。当我运行此代码,循环内的
x
id在139917134004304和139917134053248之间交替。这是因为新对象正在创建
del x[0]
x = [1]