for in loop over list (with +=) coding beginner - python

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.

Related

How these python for loop and slicing working?

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.

Updating the iterator in for loop - Python

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.

Modifying a string while looping on it

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

Infinite loops using 'for' in Python [duplicate]

This question already has answers here:
Is there an expression for an infinite iterator?
(7 answers)
Closed 5 years ago.
Why does this not create an infinite loop?
a=5
for i in range(1,a):
print(i)
a=a+1
or this
for i in range(1,4):
print(i)
i=i-1
or this
for i in range(1,4):
print(i)
i=1
Is there any way we can create infinite loops using a for loop? I know there is the while loop for that but I was just curious.
range is a class, and using in like e.g. range(1, a) creates an object of that class. This object is created only once, it is not recreated every iteration of the loop. That's the reason the first example will not result in an infinite loop.
The other two loops are not infinite because, unlike the range object, the loop variable i is recreated (or rather reinitialized) each iteration. The values you assign to i inside the loop will be overwritten as the loop iterates.
Consider a for loop:
for item in iterable:
print(item)
The idea is that as long as iterable is unchanged, we will loop through each and every item inside iterable once. For example,
for item in [3, 2, 1, 666]:
print(item)
will output 3 2 1 666. In particular, we find that range(1, 4) is a easy way to represent an iterable [1, 2, 3]. Thus,
for i in range(1, 4):
print(i)
will output 1 2 3.
Example 1
a=5
for i in range(1,a):
print(i)
a=a+1
In this case, range(1,a) is evaluated once, when the loop begins.
Example 2
for i in range(1,4):
print(i)
i=i-1
In this case, i is reevaluated every loop, before executing the print and i=i-1 statements within the body of the loop.
Example 3
for i in range(1,4):
print(i)
i=1
Just like Example 2, i is reevaluated every loop.
You can't, in this case, update the iterator that your for loop is looping over.
The range in for i in range(a): is actually a function - it takes a value, a, and returns an object that contains the values that it will loop through. Once you've built that object you can change the input variable as much as you'd like, and that object won't change.
Imagine if we made our own similar function called my_range that generates a list (whereas the built in range function generates a range):
def my_range(end):
my_list = []
for i in range(end):
my_list.append(i)
return my_list
Now if we were to use our new function, like so:
a = 4
for i in my_range(a):
print(i)
a += 1
It'd be obvious that we can't update the list object that we're looping over by changing a, because the list that we're looping over has already been made, and isn't being remade on every loop.
Can you make an infinite loop in python? Yes, just add a new entry to the object that you're looping through, e.g.:
my_list = [0]
for i in my_list:
print(i)
my_list.append(i+1)
Now we're updating the object that we're looping over.
for loops and the range(..) object
If you write for i in range(..): Python does not translate this into something like for(int i = 0; i < n; i++) (in the C-programming language family).
Furthermore the range object is constructed once, before the for loop. The range(..) object, does not know which variables have been used to construct it. Once constructed, the range is fixed.
It sees range(..) as an iterable object, and each iteration, it takes the next item the iterable yields. So whether you set the variable or not in the for loop, has no effect for the next iteration.
In python-2.x, range(..) is not a specific object, but a call to construct a list. So if you call range(10) (without the for loop), you get [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].
Why it does not work?
So then why does the examples do not work?
a=5
for i in range(1,a):
print(i)
a=a+1
Here we construct range(..) once. After that, the variables based on which it was constructed can change, since the range(..) object does change anymore. Incrementing a thus will not mean the range object will get larger.
for i in range(1,4):
print(i)
i=i-1
The for loop each time takes the next item of the iterable. So if we first have collected 1 from the range loop, the next iteration, we collect 2. This is regardless what the value of i is.
for i in range(1,4):
print(i)
i=1
For the very same reason: for does not take into account the previous value of i. It only fetches the next item the iterable (here range(..) yields). Since range(..) is fixed, it will simply feed the for loop the next item.
Emulating an infinite loop
So we need to construct an iterable that keeps yielding elements. A way to do this is itertools.count:
from itertools import count
for i in count():
# ...
pass
Or in case you are not interested in any value, we can use repeat as well:
from itertools import repeat
for _ in repeat(None):
# ...
pass
range copies the parameters given to it for internal use. So changes to those afterwards have no effect. Same as with the loop variable, which is only created from the internal values every time.
That's different though if you use a mutable object like a list to iterate over:
a = [1,2,3]
for i in a:
a.append(i)
This loop will indeed run infinitely.
Because a range is either a list (Python2) or a range object both of which are finite. That range is created once before the loop starts. Your loop variable is assigned the next element of the range at the beginning of each iteration, regardless of what you assign it later in the loop body. You need an infinite iterator for an infinite for loop, e.g. itertools.cycle:
from itertools import cycle
for x in cycle(range(5)):
# endless

How to update the original list with another list from a for-loop in a recursion in Python

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.

Categories

Resources