Question about Python scope (& enumerate function) - python

2 part question. Given the following code, my understanding is that i is a variable created within the loop function. As such, the changes have local scope and should not carry over ‘outside’ the function.
list = [3,4,5,6,7]
for element in list:
i = element * 100
print(i)
print(i)
printing i within the loop will produce the correct mathematical change. However, the second print(i) outside the loop returns 700. This technically refers to the correct reassignment of element 7 from the original list. So if changes in the loop only exist within the loop, why is it that this last one carried over ‘outside the loop’?
Furthermore, why is it that print(i) outside the loop returns the change to the last element? Why not the first element? Why not all of them? Is there some function I can call outside the loop to see the changes applied to elements 3,4,5,6?
Part 2 of my question - I know for a change to apply outside a loop, you should target the element via its index itself. Eg use for ‘element’ in range(len(list)). But can one also do this with enumerate? If so, how?
It seems that enumerate returns an object in the form of a tuple (it adds an ‘index counter’ as the first element, and keeps list as the 2nd element). And since tuples are immutable it would seem there is no way to effect a change on a global scope, is that correct?
For example, when I run the following code:
my_list = [1,2,100]
for xyz in enumerate(my_list):
xyz = 2 * xyz
print(xyz)
All it does it return to me the final element in my_list, with its index counter, concatenated to itself (‘doubled’). Eg (2,100) has become (2,100, 2,100). So is there no way to use enumerate to change elements within the original list?

To your first question, let's go through your misunderstandings one by one:
i is a variable created within the loop function
No. There is no "loop function" here. (User #0x5453 already mentioned this briefly in a comment.) The for-loop is just a language construct to facilitate iteration. Thus, i is just a variable that happens to be created, assigned, and re-assigned multiple times throughout the iteration of the for-loop.
the changes have local scope and should not carry over ‘outside’
See above. There is no other scope here. All those lines of code, starting with the list assignment and ending with that print(i) after the loop are all in the same scope. This should answer the following of your questions:
why is it that this last [change] carried over ‘outside the loop’?
why is it that print(i) outside the loop returns the change to the last element?
The variable i was re-assigned multiple times. In the last iteration of the for-loop, the value assigned to i was 7 * 100, i.e. 700. Then the loop ends, and nothing else happens to i, so it still holds the value 700.
To the second question:
for a change to apply outside a loop, you should target the element via its index itself
If you refer to the your list, then the loop has nothing to do with that at all. If you want to change an element of the list, yes, re-assignment only works by targeting the index of that element in the list:
my_list = [1, 2, 100]
my_list[2] = 420
print(my_list) # output: [1, 2, 420]
can one also do this with enumerate?
No, enumerate is something else entirely. Calling enumerate on my_list returns an iterator, which can then be used in a for-loop just like the original list can, but each element that iterator produces is (in its simplest form) a 2-tuple, where the second element is an element from my_list and the first element is the index of that element.
Try this:
for tup in enumerate(my_list):
print(tup[0], tup[1])
This will give you:
0 1
1 2
2 420
Since tuples are iterables, they support unpacking, which is why it is common to go for more readability and instead do this:
for idx, element in enumerate(my_list):
print(idx, element)
The output is the same.
Nothing about this changes my_list. All these operations do, is iterate over it in some way, which just produces elements from it one by one.
Now, you do this:
for xyz in enumerate(my_list):
xyz = 2 * xyz
Since xyz is just that index-element-tuple produced by enumerate, multiplying it by 2 just concatenates it with itself, as you correctly noted, creating a new tuple and re-assigning it to xyz. In the next iteration it gets overwritten again by what enumerate produces, then re-assigned again by you and so on. Again, none of that changes my_list, it just changes that xyz variable.
If I understood you correctly, you want to be able to mutate your list within a loop over its elements. While this can quickly lead to dangerous territory, a simple working example would be this:
for idx, element in enumerate(my_list):
my_list[idx] = 2 * element
print(my_list)
The output:
[2, 4, 840]
Now we actually change/re-assign specific elements in that list. As before, assignment only works with an index. But the index is what we get as the first element of the 2-tuple provided by enumerate in this case.
However, there are sometimes (arguably) more elegant ways to accomplish the same thing. For instance, we could use list comprehension to achieve the same result:
my_list = [element * 2 for element in my_list]
Here we overwrite my_list with a new one that we created through list comprehension iterating over the original list.
Hope this helps clear up some misconceptions.

Related

What exactly is del doing here? [duplicate]

This question already has answers here:
Difference between del, remove, and pop on lists
(14 answers)
Closed 3 years ago.
I'm learning python and I wanted to create some code which would take a list of lists, check if each row had a particular value for a given index number and if it did, delete the entire row. Now I intuitively decided to use del to delete the rows, but when I printed out the list with the removed values, I got the same list back. I added a counter to check if there were any rows which had the value to be removed and they did indeed exist. The problem I have is illustrated by the following code:
test_list=[1,2,3,4,5]
for element in test_list:
if element == 1:
del element
print (test_list)
Output
[1,2,3,4,5]
What is the function of del, if not to delete the element here?
Actually, del is doing nothing in your code. It's just deleting a local binding to the iteration variable (not to the list element, even though they point to the same object). This has no effect on the list, and anyway in the next iteration the variable will get assigned to the next value.
For the general case, if you wanted to delete an element in the list using del (and you didn't know where it's located), this is what you'd have to do:
delete_me = 1
for i in range(len(test_list)-1, -1, -1):
if test_list[i] == delete_me:
del test_list[i]
Notice how del works for lists, and the fact that we need to traverse the list in reverse to avoid problems when modifying a list at the same time we're iterating over it.
There is a simpler way, though - and that is not using del at all, but a list comprehension:
delete_me = 1
test_list = [x for x in test_list if x != delete_me]
del is removing the reference to the object element. Which is not the reference to the list element.
You are deleting a temporary variable named element which is created at each iteration on test_list.
If you want to remove an item from a list, use remove, or del list[index]
More here: https://www.tutorialspoint.com/python/list_remove.htm
How to remove an element from a list by index?
Is there a simple way to delete a list element by value?
The del keyword is used to delete objects. In Python everything is an object, so the del keyword can also be used to delete variables, lists, or parts of a list etc.
https://www.w3schools.com/python/ref_keyword_del.asp

Initialize a list using inline for loop

I am initializing my list object using following code.
list = [
func1(centroids[0],value),
func1(centroids[1],value),
....,
func1(centroids[n],value)]
I am trying to do it a more elegant way using some inline iteration. Following is the pseudo code of one possible way.
list = [value for value in func1(centroids[n],value)]
I am not clear how to call func1 in an iterative way. Can you suggest a possible implementation?
For a list of objects, Python knows how to iterate over it directly so you can eliminate the index shown in most of the other answers,
res = [func1(c, value) for c in centroids]
That's all there is to it.
A simple list comprehension consists of the "template" list element, followed by the iterator needed to step through the desired values.
my_list = [func1(centroids[0],value)
for n in range(n+1)]
Use this code:
list = [func1(centroids[x], value) for x in range(n)]
This is called a list comprehension. Put the values that you want the list to contain up front, then followed by the for loop. You can use the iterating variable of the for loop with the value. In this code, you set up n number(s) of variable(s) from the function call func1(centroids[x], value). If the variable n equals to, let's say, 4, list = [func1(centroids[0], value), func1(centroids[0], value), func1(centroids[0], value), func1(centroids[0], value)] would be equal to the code above

Append select dict keys to a list [duplicate]

I am trying to append objects to the end of a list repeatedly, like so:
list1 = []
n = 3
for i in range(0, n):
list1 = list1.append([i])
But I get an error like: AttributeError: 'NoneType' object has no attribute 'append'. Is this because list1 starts off as an empty list? How do I fix this error?
This question is specifically about how to fix the problem and append to the list correctly. In the original code, the reported error occurs when using a loop because .append returns None the first time. For why None is returned (the underlying design decision), see Why do these list operations return None, rather than the resulting list?.
If you have an IndexError from trying to assign to an index just past the end of a list - that doesn't work; you need the .append method instead. For more information, see Why does this iterative list-growing code give IndexError: list assignment index out of range? How can I repeatedly add elements to a list?.
If you want to append the same value multiple times, see Python: Append item to list N times.
append actually changes the list. Also, it takes an item, not a list. Hence, all you need is
for i in range(n):
list1.append(i)
(By the way, note that you can use range(n), in this case.)
I assume your actual use is more complicated, but you may be able to use a list comprehension, which is more pythonic for this:
list1 = [i for i in range(n)]
Or, in this case, in Python 2.x range(n) in fact creates the list that you want already, although in Python 3.x, you need list(range(n)).
You don't need the assignment operator. append returns None.
append returns None, so at the second iteration you are calling method append of NoneType. Just remove the assignment:
for i in range(0, n):
list1.append([i])
Mikola has the right answer but a little more explanation. It will run the first time, but because append returns None, after the first iteration of the for loop, your assignment will cause list1 to equal None and therefore the error is thrown on the second iteration.
I personally prefer the + operator than append:
for i in range(0, n):
list1 += [[i]]
But this is creating a new list every time, so might not be the best if performance is critical.
Note that you also can use insert in order to put number into the required position within list:
initList = [1,2,3,4,5]
initList.insert(2, 10) # insert(pos, val) => initList = [1,2,10,3,4,5]
And also note that in python you can always get a list length using method len()
Like Mikola said, append() returns a void, so every iteration you're setting list1 to a nonetype because append is returning a nonetype. On the next iteration, list1 is null so you're trying to call the append method of a null. Nulls don't have methods, hence your error.
use my_list.append(...)
and do not use and other list to append as list are mutable.

Python, change value of the arguments within the function

I'm trying to change the value of the list that i put as argument in the function.
this is the code:
def shuffle(xs,n=1):
if xs: #if list isn't empty
if n>0:
#gets the index of the middle of the list
sizel=len(xs)
midindex=int((sizel-1)/2)
for times in range(n):
xs=interleave(xs[0:midindex],xs[midindex:sizel])
return None
The interleave code returns a list with the values of both lists mixed up.
However when i run:
t=[1,2,3,4,5,6,7]
shuffle(t,n=2)
print t
The list t didn't changed it's order. The function needs to return None so i can jst use t=shuffle(t,n). There's anyway i can do this?
Your problem is right here:
xs=interleave(xs[0:midindex],xs[midindex:sizel])
You're making slices of the list to pass to your interleave() function. These are essentially copies of part of the list. There's no way that what comes back from the function can be anything than a different list from xs.
Fortunately, you can just reassign the new list you get back into the original list. That is, keep xs pointing to the same list, but replace all the items in it with what you get back from the interleave() function.
xs[:]=interleave(xs[0:midindex],xs[midindex:sizel])
This is called a slice assignment. Since xs remains the same list that was passed in, all references to the list outside the function will also see the changes.
xs is a reference local to the function, and is independant of t. When you reassign xs, t still points to the original list.
Since you must not return anything from the function, a workaround is to keep a reference to the original list and repopulate it using slice assignment:
orig_xs = xs
# do stuff here
orig_xs[:] = xs

Why is a sublist mutable in a for loop?

I am a beginner in python and I find this about mutabilty quite confusing and non intuitive. Given a list:
lst = [[1, 2, 3], [4, 5, 6]]
And trying to change the list within a for-loop.
for i in lst:
i = "test"
I understand that this does not change the list. But:
for i in lst:
i[1] = "test"
I was surprised that referring to the sublist led to following outcome:
[[1, 'test', 3], [4, 'test', 6]]
I tried to understand with a visualizer but I don't get it. Would anybody please explain this in plain words? Thank you.
In the first case, you simply have a copied reference to the element.
i ---> lst[n]
In the latter case, you are dereferencing the reference, and changing data (not in a copy):
i[i] ---> lst[n][i]
Therefore assigning to i[n] will point to the actual mutable object, not a copy of it.
Assignment (which is what the = operator does) assigns a value to a name.
i is a variable name in the local namespace. At the time it runs inside the for loop, it refers to a list. By assigning a string to it, you cause the name to no longer refer to a list, but instead refer to the string. The list is unaffected -- the only thing that changes is the value associated with the name.
i[1] is a name that specifies a specific location inside one of the two lists, depending on what i is set to at the time. By assigning a string to it, you cause the name to no longer refer to the object that previously occupied that space inside the list (an integer, 2 or 5 depending on the list) and instead refer to the string. The integer is unaffected -- the only thing that changes is the value associated with the name.
So in each case, assignment is doing the same thing -- it's making a name refer to a new thing instead of the old thing it referred to. The difference is that the second case is a special name in that it refers to a property of a mutable object.
for does not make copies of each element it yields. As such, the yielded object retains all the properties of the original (since it is the original), including mutability.
since for loop in case of string is called in different way
like
for i in lst:
it means if lst is list or array of string then i is referenced to the value of x[i] directly that is why in first case result was "test " two times because length of "lst" was just 2
while in second case i[1] means
i ------lst[i]
i[1]--------`-lst[i][1] means
first value equal lst[0][1]---[1,'test',3]
second value equal lst[1][1]--[4,'test',6]

Categories

Resources