Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

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

Example 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.

Example 2 runs 5 times even though len(x)==1.

So my question is, does enumerate generate a full list of (index, value) pairs at the beginning of the loop and iterate through it? Or are they generated on each iteration of the loop?

like image 608
dbdq Avatar asked Mar 07 '17 17:03

dbdq


People also ask

What does enumerate () do in Python?

Python enumerate() Function The enumerate() function takes a collection (e.g. a tuple) and returns it as an enumerate object. The enumerate() function adds a counter as the key of the enumerate object.

Why is enumerate better than range Python?

Instead of using the range() function, we can instead use the built-in enumerate() function in python. enumerate() allows us to iterate through a sequence but it keeps track of both the index and the element. The enumerate() function takes in an iterable as an argument, such as a list, string, tuple, or dictionary.

Is enumerate better than range?

enumerate is faster when you want to repeatedly access the list/iterable items at their index. When you just want a list of indices, it is faster to to use len() and range (xrange in Python 2. x).

Is enumerate faster than for loop Python?

Using enumerate() is more beautiful but not faster for looping over a collection and indices. Mind your Python version when looping over two collections - use itertools for Python 2.


3 Answers

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. 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.

like image 196
Wisperwind Avatar answered Oct 17 '22 21:10

Wisperwind


enumerate() returns an iterator, or some other object which supports iteration. 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.

__next__() returns the next item from the container. If there are no further items, raise the StopIteration exception.

Does enumerate() generate a full list of (index, value) pairs at the beginning of the loop and iterates through it? 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() doesn't create a full list at the beginning of the loop.

As, @Wisperwind mentioned, in your second case, you're assigning a new object to the name x. The object, the loop iterates over does not change during the iteration.

like image 23
Wasi Ahmad Avatar answered Oct 17 '22 20:10

Wasi Ahmad


Just a clarification to what Wasi Ahmad and Wisperwind have said. Both state that "you're only assigning a new object to the name 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

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. When you're going through your loop, it is iterating over the original instance of x, regardless of whether you can still reference it.

As you can see, y points to the original 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.

like image 8
FuriousGeorge Avatar answered Oct 17 '22 20:10

FuriousGeorge