简体   繁体   English

Python枚举的奇怪行为

[英]Python strange behavior with enumerate

I know I'm not supposed to modify the list inside a loop, but just out of curiosity, I would like to know why the number of iterations is different between the following two examples. 我知道我不应该在循环中修改列表,但出于好奇,我想知道为什么以下两个示例之间的迭代次数不同。

Example 1: 例1:

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

Example 2: 例2:

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

Example 1 runs only 3 times because when i==3 , len(x)==2 . 示例1仅运行3次,因为当i==3len(x)==2

Example 2 runs 5 times even though len(x)==1 . 即使len(x)==1示例2也运行5次。

So my question is, does enumerate generate a full list of (index, value) pairs at the beginning of the loop and iterate through it? 所以我的问题是, enumerate是否在循环开始时生成(index, value)对的完整列表并迭代它? Or are they generated on each iteration of the loop? 或者它们是在循环的每次迭代中生成的?

In the first example, you're actually modifying the list you're iterating over. 在第一个示例中,您实际上正在修改正在迭代的列表。

On the other hand, in the second case, you're only assigning a new object to the name x . 另一方面,在第二种情况下,您只是将新对象分配给名称x The object the loop iterates over does not change, though. 但是,循环迭代的对象不会改变。

Have a look at http://foobarnbaz.com/2012/07/08/understanding-python-variables/ for a more detailed explanation about names and variables in Python. 有关Python中名称和变量的更详细说明,请查看http://foobarnbaz.com/2012/07/08/understanding-python-variables/

enumerate() returns an iterator, or some other object which supports iteration. enumerate()返回一个迭代器或一些支持迭代的其他对象。 The __next__() method of the iterator returned by enumerate() returns a tuple containing a count (from start which defaults to 0) and the values obtained from iterating over iterable . enumerate()返回的迭代器的__next __()方法返回一个包含count的元组(从start开始,默认为0)和迭代迭代得到的值。

__next__() returns the next item from the container. __next __()返回容器中的下一个项目。 If there are no further items, raise the StopIteration exception. 如果没有其他项,请提高StopIteration异常。

Does enumerate() generate a full list of (index, value) pairs at the beginning of the loop and iterates through it? enumerate()是否在循环开始时生成(索引,值)对的完整列表并迭代它? Or are they generated on each iteration of the loop? 或者它们是在循环的每次迭代中生成的?

So, enumerate() returns an iterator and at every iteration, __next__() checks if there are further items. 因此, enumerate()返回一个迭代器,并且在每次迭代时, __next__()检查是否还有其他项。 enumerate() doesn't create a full list at the beginning of the loop. enumerate()不会在循环开头创建完整列表。

As, @Wisperwind mentioned, in your second case, you're assigning a new object to the name x . 正如@Wisperwind所提到的那样,在你的第二种情况下,你要为名称x指定一个新对象。 The object, the loop iterates over does not change during the iteration. 循环迭代的对象在迭代期间不会改变。

Just a clarification to what Wasi Ahmad and Wisperwind have said. 只是澄清了Wasi Ahmad和Wisperwind所说的话。 Both state that "you're only assigning a new object to the name x". 两者都声明“您只是将新对象分配给名称x”。 This might be slightly confusing as it might be interpreted as saying "you're creating a new object ( [1] ) and storing it to the name x , to which you'd say "Well yah, so why isn't it changing?!" To see what's happening, print out the id of the object 这可能有点令人困惑,因为它可能被解释为“你正在创建一个新对象( [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

You'll notice that the id of x is changing each time through the loop and when you're finished with the loop, x will point to the last modification made in the loop. 你会注意到每次循环时xid都在变化,当你完成循环时, x将指向循环中的最后一次修改。 When you're going through your loop, it is iterating over the original instance of x, regardless of whether you can still reference it. 当你完成循环时,它会迭代x的原始实例,无论你是否仍然可以引用它。

As you can see, y points to the original x . 如您所见, y指向原始x As you make your iterations through the loop, even though x is changing, y is still pointing to the original x which is still being looped over. 当您在循环中进行迭代时,即使x正在改变, y仍然指向仍在循环的原始x

Indeed: Your first snippet modifies the iterated-over list in place; 确实:您的第一个代码段修改了迭代列表; the second points the variable x to a new list, leaving unmodified the list transversed by enumerate() . 第二个将变量x指向一个新列表,保留未修改的enumerate()的列表。 You can see this in action by going to the following links on www.pythontutor.com, which allow you to single-step over your code and visualize the contents of your variables: 您可以通过访问www.pythontutor.com上的以下链接来查看此操作,该链接允许您单步执行代码并可视化变量的内容:

To better see what's going on, go here instead to step over the following expanded code: 为了更好地了解发生了什么,请转到此处以跳过以下扩展代码:

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

Others have already pointed out that your second example only changes the value to which x points, but not the list over which you're iterating. 其他人已经指出,你的第二个例子只改变了x指向的值,而不是你要迭代的列表。 This is a perfect example for the difference between ordinary assignment ( x = [1] ) and slice assignment ( x[:] = [1] ). 这是普通分配( x = [1] )和切片分配x[:] = [1] )之间差异的完美示例。 The latter modifies the list x points to in-place : 后者将列表x点修改为就地

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

will print 将打印

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

The list [1, 2, 3, 4, 5] is 'tagged' with x 列表[1, 2, 3, 4, 5]x标记'

for i, s in enumerate(x):

enumerate() attaches another tag, so [1, 2, 3, 4, 5] is now tagged x and y . enumerate()附加另一个标记,因此[1, 2, 3, 4, 5]现在被标记为xy enumerate() will continue using the y tag, not the x tag. enumerate()将继续使用y标记,而不是x标记。

del x[0]

The list stored in memory is modified, so x and y now both refer to [2, 3, 4, 5] 存储在内存中的列表被修改,因此xy现在都引用[2, 3, 4, 5]

Alternatively, when you use 或者,当你使用时

x = [1]

A new list [1] is created in memory, and the x tag now points to that. 在内存中创建了一个新列表[1]x标签现在指向该列表。 The y tag is still pointing to the original list. y标记仍指向原始列表。

How Python variable work: Python变量的工作原理:
http://foobarnbaz.com/2012/07/08/understanding-python-variables/ http://foobarnbaz.com/2012/07/08/understanding-python-variables/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM