Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I use a list index as an indexing variable in a for loop? [duplicate]

I have the following code:

a = [0,1,2,3]  for a[-1] in a:   print(a[-1]) 

The output is:

0 1 2 2 

I'm confused about why a list index can be used as an indexing variable in a for loop.

like image 929
Kundan Verma Avatar asked Apr 12 '19 03:04

Kundan Verma


People also ask

How do I find the index of a duplicate element in a list?

Method #1 : Using loop + set() In this, we just insert all the elements in set and then compare each element's existence in actual list. If it's the second occurrence or more, then index is added in result list.

What function allows us to access the index of each element in the for loop?

Use the python enumerate() function to access the index in for loop. enumerate() method is an in-built method in Python, which is a good choice when you want to access both the items and the indices of a list. enumerate() method is the most efficient method for accessing the index in a for loop.


2 Answers

List indexes such as a[-1] in the expression for a[-1] in a are valid as specified by the for_stmt (and specifically the target_list) grammar token, where slicing is a valid target for assignment.

"Huh? Assignment? What has that got to do with my output?"

Indeed, it has everything to do with the output and result. Let's dive into the documentation for a for-in loop:

for_stmt ::=  "for" target_list "in" expression_list ":" suite 

The expression list is evaluated once; it should yield an iterable object. An iterator is created for the result of the expression_list. The suite is then executed once for each item provided by the iterator, in the order returned by the iterator. Each item in turn is assigned to the target list using the standard rules for assignments (see Assignment statements), and then the suite is executed.

(emphasis added)
N.B. the suite refers to the statement(s) under the for-block, print(a[-1]) in our particular case.

Let's have a little fun and extend the print statement:

a = [0, 1, 2, 3] for a[-1] in a:     print(a, a[-1]) 

This gives the following output:

[0, 1, 2, 0] 0    # a[-1] assigned 0 [0, 1, 2, 1] 1    # a[-1] assigned 1 [0, 1, 2, 2] 2    # a[-1] assigned 2 [0, 1, 2, 2] 2    # a[-1] assigned 2 (itself) 

(comments added)

Here, a[-1] changes on each iteration and we see this change propagated to a. Again, this is possible due to slicing being a valid target.

A good argument made by Ev. Kounis regards the first sentence of the quoted doc above: "The expression list is evaluated once". Does this not imply that the expression list is static and immutable, constant at [0, 1, 2, 3]? Shouldn't a[-1] thus be assigned 3 at the final iteration?

Well, Konrad Rudolph asserts that:

No, [the expression list is] evaluated once to create an iterable object. But that iterable object still iterates over the original data, not a copy of it.

(emphasis added)

The following code demonstrates how an iterable it lazily yields elements of a list x.

x = [1, 2, 3, 4] it = iter(x) print(next(it))    # 1 print(next(it))    # 2 print(next(it))    # 3 x[-1] = 0 print(next(it))    # 0 

(code inspired by Kounis')

If evaluation was eager, we could expect x[-1] = 0 to have zero effect on it and expect 4 to be printed. This is clearly not the case and goes to show that by the same principle, our for-loop lazily yields numbers from a following assignments to a[-1] on each iteration.

like image 90
TrebledJ Avatar answered Sep 23 '22 05:09

TrebledJ


(This is more of a long comment than an answer - there are a couple of good ones already, especially @TrebledJ's. But I had to think of it explicitly in terms of overwriting variables that already have values before it clicked for me.)

If you had

x = 0 l = [1, 2, 3] for x in l:     print(x) 

you wouldn't be surprised that x is overridden each time through the loop. Even though x existed before, its value isn't used (i.e. for 0 in l:, which would throw an error). Rather, we assign the values from l to x.

When we do

a = [0, 1, 2, 3]  for a[-1] in a:   print(a[-1]) 

even though a[-1] already exists and has a value, we don't put that value in but rather assign to a[-1] each time through the loop.

like image 44
Nathan Avatar answered Sep 19 '22 05:09

Nathan