Reputation: 41
It's agreed upon that the iterable item in a for
loop is evaluated only once. Hence, the output f called
is printed once.
What I'm struggling to figure out is, why am I not getting an out-of-bound error
? Since at the time of the evaluation, the length was equal to 100
.
mylist = [x for x in range(100)]
def printmylist(thelist):
print("f called")
return thelist
for i, item in enumerate(printmylist(mylist)):
print(len(mylist))
del mylist[len(mylist) - i - 1]
f called 100 99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51
Upvotes: 4
Views: 202
Reputation: 30813
Though the print("f called")
is only called once, the variable (called iterable object
which stores the evaluation value of enumerate(printmylist(mylist))
in your case has its referred set
change from time to time because you remove the enumeration one by one. At first you have all the 100 items, but it is reduced one by one:
Note: len(mylist) - i - 1 = 99, delete index 99 -> 100 removed
1 2 3 ... 98 99 100 #len(mylist) = 100
^ <-- here is i = 0
Note: len(mylist) - i - 1 = 98, delete index 98 -> 99 removed
1 2 3 ... 98 99 #len(mylist) = 99
^ <-- here is i = 1
Note: len(mylist) - i - 1 = 97, delete index 97 -> 98 removed
1 2 3 ... 98 #len(mylist) = 98
^ <-- here is i = 2
... and so on
Plus, since i
will be checked every end of the loop against that variable (the iterable object
, therefore, you only print 50
items out of 100
items.
From the Python for statement documentation:
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 of ascending indices. Each item in turn is assigned to the target list using the standard rules for assignments, and then the suite is executed. When the items are exhausted (which is immediately when the sequence is empty), the suite in theelse
clause, if present, is executed, and theloop
terminates.
To simplify, imagine you have
v = enumerate(printmylist(mylist)) #v is the iterable object
Though it is only evaluated once, the set
where v
is pointed to changes every time you remove an item but i
iterates over v
. Thus, i
is going to be increasing increasing till finally i = 50
.
.
.
.
v = ... 50, i = 49
^ <-- here is i and is in v
v = ... 50 , i = 50 and is out from v
^ <-- no more item found
No more item is left to be evaluated beyond the border. Thus you get the results:
100 99 ... 51
And not getting out of bounds.
More on how for works internally, including how it creates internal indexer and checks it against the value pointed by the termination value every time.
Upvotes: 2
Reputation: 2181
In the for
loop, you use the current length of the list to evaluate the location to delete. And, you delete from the end of the list. So your loop only iterates 50 times.
If you change the print
statement to display i
, you will find it only counts from 0 to 49.
Upvotes: 2