I stumbled upon this conundrum and would like to know how Python is working that causes these different outputs from the 2 sets of code. Here are the 2 for loop codes:
First case
l = [1,2,3]
for i in l:
print(i)
l = 0
Second case
l = [1,2,3]
for i in l:
print(i)
l.append(3)
For the first scenario, the output is 1, 2, 3, even though I was expecting that it should return an error since l would be updated to 0. So I tried to see what the output might be if I were to append an item to l, and it does work as I expected, which is that l does get updated, and prints out 1,2,3,3,3,3,3,3,.... infinitely.
Could someone explain why just reassigning l to something doesn't get reflected in the for loop but appending an item to l does get reflected in the for loop?
Thank you!
This is the difference between re-binding a variable name and mutating the underlying object.
Case 1: You rebind the name l to a new value, but that doesn't change the list object, which is currently iterated through.
Case 2: You actually mutate the same list object.
When you are assigning the value l=0 inside the loop, you are actually creating a new variable l that is local inside the loop, you can verify this by printing the id of the variable inside the loop which represents the memory address for the variable reference:
l = [1, 2, 3]
for i in l:
print(id(l))
print(i)
l = 0
print(id(l))
1927830512328 ->id before assigning l=0
1
140728450314480 ->id after assigning l=0
140728450314480
2
140728450314480
140728450314480
3
140728450314480
It is recommended that you print the address value. You can check the address values from l to id(l).
In the first case, if you run l = 0 in the for statement and output id(l), a value that differs from the initial address value is displayed. This indicates that the existing l and l in the for statement are different variables.
In the second case, the address value does not change. This is because the original address was accessed through a function called append and added a value.
Related
a = [1,2,3]
for num in a:
a = a + [num]
print(a)
>>>[1,2,3,1,2,3]
a = [1,2,3]
for num in a:
a += [num]
print(a)
>>>
The first code works as expected, so I assume the below code will work the same, but it didn't print anything. Not even a Error message.
Question:
I did some research in stackoverflow on the use of +=, but still got confused on what's the difference between the add and iadd
In the first case, you are rebinding the name a to a new value, so the variable a before the loop is not the same object as the variable a inside and after the loop. The loop is able to iterate on the original value of a.
But in the second case, you are not rebinding the name. a is the same object throughout the code. And so the loop iterates over a list that grows endlessly bigger.
How this statement will work?
j for i in range(5)
Example:
x1=(j for i in range(5))
will yield five 4's when iterated. Why it will not yield 01234 like when we replace j with i.
How this statement working?
a = [0, 1, 2, 3]
for a[-1] in a:
print(a[-1])
0
1
2
2
Your first example is a generator expression (the parentheses are required, so the first version you show doesn't actually make sense). The generator expression repeatedly yields j, which is not defined in the code you show. But from your description, it already has the value 4 in the environment you were testing in, so you see that value repeatedly. You never use the i value, which is what's getting the values from the range.
As for your other loop, for a[-1] in a keeps rebinding the last value in the list, and then printing it.
A for loop does an assignment to the target you give it between for and in. Usually you use a local variable name (like i or j), but you can use a more complicated assignment target, like self.index or, as in this case, a[-1]. It's very strange to be rewriting a value of your list as you iterate over it, but it's not forbidden.
You never get 3 printed out, because each of the previous assignments as you iterated overwrote it in the list. The last iteration doesn't change the value that gets printed, since you're assigning a[-1] to itself.
Your first question raises error when iterating, as j is not defined. If you are getting any response from that, you probably have defined a value for j above your generator, which sounds to be 4 in your code.
About your second question:
when you have something like this:
for i in a:
...
Actually you are are doing this in each cycle: i=a[0], i=a[1], ....
When you write a[-1] in a, it is kind of equal to: a[-1]=a[0], a[-1]=a[1], ... and you are changing last element of a each time. So at the end, the last element of your list would be 2.
Here we modify the string s while looping on it:
s = 'hello'
for c in s:
s += c
print(s)
It doesn't generate an infinite loop (it could: we add a new character in each iteration!).
I know this since years, but I can't really explain why. Has this something to do with string immutability or is it not related?
When doing for c in s:, is the original s copied in memory before starting the loop?
Another case that I thought could generate an infinite loop, but it does not:
s = 'hello'
def f(x):
i = 0
while True:
try:
yield x[i]
except IndexError:
break
i += 1
for c in f(s):
s += c
print(s)
It doesn't generate an infinite loop (it could: we add a new character in each iteration!).
It is because strings in Python are immutable.
When you do for c in s, for loop iterates over original object (object containing "hello") and when you do s += c, a new string is created and hence, you aren't modifying the original string object that for loop is still iterating over.
Also, adding #Steven Rumbalski 's comment in my answer:
It iterates over the original object. Rebinding the name to a new object doesn't change the for-loop because it doesn't resolve the name on each iteration.
Now, Let's talk about second example of yours:
If you call your function f(s), it makes it's formal argument x to point to the object containing the string "hello" and it creates a generator using the string object. Now, subsequent re-assignment of the string s (using s += c) actually creates another string and doesn't effect the original object. Your function's formal argument is still pointing to an object containing "hello".
So, after first iteration of your for loop in the second example:
Formal argument of function f(x) will be still pointing to an object containing "hello".
s will be pointing to an object containing "helloh".
IMPORTANT:
However, if you try your code with mutable data types, it may create an infinite loop. See this:
li = [ 1, 2, 3, 4 ]
for i in li:
li.append(i)
print(li)
Mutate a list to accomplish what you want.
array = list('hello')
for c in array:
array.append(c)
print(*array, sep='')
prints
helloh
hellohe
hellohel
hellohell
hellohello
hellohelloh
hellohellohe
hellohellohel
hellohellohell
hellohellohello
hellohellohelloh
hellohellohellohe
...
If you want to loop by one character continuously you can do
from itertools import cycle
for c in cycle('hello'):
print(c)
prints
h
e
l
l
o
h
e
l
...
This is not a recursive method. It is perfectly fine. It will run only once because you pass a string f('hello') now loop has "hello" to iterate. so it will run only once
Let's say I have a list, L = [a,b,c,d], and a copy of this list, and also a for-loop that has a recursion code in it:
for item in L:
if:
*some base code*
Lcopy.remove(item)
L = []
L += Lcopy[:]
else:
*some recursion code*
return ...
But after the every recursion is done, L goes back to how it was originally. I know that once the code goes into the recursion, Python gives a new memory address to the list (L) that it uses, but is it possible to make the original L, the very first L, to update and get rid of the values that was was supposed to be deleted?
The problem is in L = []
This is the moment after L is pointing to another value, a newly created list.
Python doesn't do anything special with references in recursion. Lists are always passed by reference. Assignment breaks the reference of L to the old list.
When you have L = [] you're re-assigning that function's local name L to a completely new list. If you instead want to empty out the same list, you need to delete all of its contents, like so: del L[:]
For a more detailed explanation of Python's execution model, and what assigning to a variable actually does, check out this blog post by Jeff Knupp.
I'm playing with for loops in Python and trying to get used to the way they handle variables.
Take the following piece for code:
a=[1,2,3,4,5]
b=a
b[0]=6
After doing this, the zeroth element of both b and a should be 6. The = sign points a reference at the array, yes?
Now, I take a for loop:
a=[1,2,3,4,5]
for i in a:
i=6
My expectation would be that every element of a is now 6, because I would imagine that i points to the elements in a rather than copying them; however, this doesn't seem to be the case.
Clarification would be appreciated, thanks!
Everything in python is treated like a reference. What happens when you do b[0] = 6 is that you assign the 6 to an appropriate place defined by LHS of that expression.
In the second example, you assign the references from the array to i, so that i is 1, then 2, then 3, ... but i never is an element of the array. So when you assign 6 to it, you just change the thing i represents.
http://docs.python.org/reference/datamodel.html is an interesting read if you want to know more about the details.
That isn't how it works. The for loop is iterating through the values of a. The variable i actually has no sense of what is in a itself. Basically, what is happening:
# this is basically what the loop is doing:
# beginning of loop:
i = a[0]
i = 6
# next iteration of for loop:
i = a[1]
i = 6
# next iteration of for loop:
i = a[2]
i = 6
# you get the idea.
At no point does the value at the index change, the only thing to change is the value of i.
You're trying to do this:
for i in xrange(len(a)):
a[i] = 6 # assign the value at index i
Just as you said, "The = sign points a reference". So your loop just reassigns the 'i' reference to 5 different numbers, each one in turn.