Python: Using del in for loops - python

I was iterating through a list with a for loop, when I realized del seemed to not work. I assume this is because i is representing an object of the for loop and the del is simply deleting that object and not the reference.
And yet, I was sure I had done something like this before and it worked.
alist = [6,8,3,4,5]
for i in alist:
if i == 8:
del i
In my code its actually a list of strings, but the result is the same: even though the if conditional is satisfied, deleting i has no effect.
Is there a way I can delete a number or string in this way? Am I doing something wrong?
Thanks.

Your idea as to why you are seeing that behavior is correct. Hence, I won't go over that.
To do what you want, use a list comprehension to filter the list:
>>> alist = [6,8,3,4,5]
>>> [x for x in alist if x != 8]
[6, 3, 4, 5]
>>> alist = [6,8,8,3,4,5]
>>> [x for x in alist if x != 8]
[6, 3, 4, 5]
>>>
This approach is also a lot more efficient than a for-loop.

The for loop assigns a new value to i at each run.
So, essentially, your for loop above does
i = 6
i = 8
del i
i = 3
i = 4
i = 5
which has no effect.

del does not delete an object. It simply decrements the reference count of the object referenced by its argument. In your code
alist = [6,8,3,4,5]
for i in alist:
if i == 8:
del i
you have 6 objects: 5 separate integers, and a list of 5 references (one per integer). The for loop works by executing its body once per element in alist, with i holding a reference to a different element in alist in each iteration. Whichever object is referenced by i has a reference count of at least 2: the reference held by alist and i itself. When you call del i, you are simply decrementing its reference count by making i point to nothing.
While the following techinically works, by deleting all (known) references to the object, it has its own problems (involving modifying a list you are currently iterating over) and should not be used.
>>> alist=[6,8,3,4,5]
>>> for i, a in enumerate(alist):
... if a == 8:
... del a # delete the loop index reference
... del alist[i] # delete the reference held by the list
>>> alist
[6,3,4,5]
Instead, simply use a list comprehension to build a new list to replace the old one
alist = [ x for x in alist if x != 8 ]

If you really want to use del you need to use it on the list:
del alist[i]
(note that in this case i is an index, not the value you want to remove)
But really here you should probably just create another list using list comprehension:
[x for x in alist if x != 8]

Related

How can I remove all the numbers in a list except for 1 number (Python)?

I want my code to remove every single number in a list except for a specific number, which is 3. Instead it removes certain numbers but majority of them still remain in the list.
myList = [0,1,2,3,4,5]
i = 0
for i in myList:
print(i)
if i != 3:
myList.remove(i)
else:
continue
i += 1
print(myList)
You've got a few issues. First, you're trying to modify the list in place, as you're trying to process it. You can fix that by changing:
for i in myList:
to:
for i in myList[:]:
That will allow your code to give the desired result. It makes a "copy" of the list over which to iterate, so you can modify the original list without messing up your iteration loop.
The other thing to note is that you assign a value to i in the for loop, but then you manually change it after your if-else block. That change gets discarded when you go back to the top of the loop.
Also, you're else: continue prevents incrementing i, but it doesn't matter, because that incremented value was just getting tossed anyway.
So... commenting out some of the unnecessary stuff gives:
myList = [0,1,2,3,4,5]
# i = 0
for i in myList[:]:
print(i)
if i != 3:
myList.remove(i)
# else:
# continue
# i += 1
print(myList)
You have a couple of problems. First, the for loop iterates the list values, not its index. You can use enumerate to get both the index and the value. Second, if you delete values in a list, its remaining elements are shifted down by 1 but since the iterator also increments by 1, you miss a value. A trick is to iterate the list in reverse so that any deleted values have already been iterated.
>>> myList = [0,1,2,3,4,5]
>>> mlen = len(myList)
>>> for i, v in enumerate(reversed(myList), 1):
... if v != 3:
... del myList[mlen-i]
...
>>>
>>> myList
But this operation is slow. If you don't need to modify the original list, use a list comprehension
>>> myList = [0,1,2,3,4,5]
>>> myList = [v for v in myList if v==3]
>>> myList
[3]
The issue is you are removing elements from the list you are iterating over. So your loop won't iterate over the entire list.

How to remove an item in list once used from a large list in python to save the memory?

If i have large list which runs in millions of items, i want to iterate through each of them. Once i use the item it will never be used again, so how do i delete the item from the list once used? What is the best approach?
I know numpy is fast and efficient but want to know how it can be done using normal list.
mylst = [item1, item2,............millions of items]
for each_item in mylist:
#use the item
#delete the item to free that memory
You cannot delete an object directly in Python - an object's memory is automatically reclaimed, by garbage collection, when it's no longer possible to reference the object. So long as an object is in a list, it may be referenced again later (via the list).
So you need to destroy the list too. For example, like so:
while mylst:
each_item = mylst.pop() # removes an object from the end of the list
# use the item
Assuming you can copy a list (memory constraints might cause issues here) and only need to remove specific elements from it, you can create a shallow copy of the list and remove elements from it while iterating through the original list:
a_list = [1, 2, 3, 4, 5]
b_list = a_list.copy()
removal_key = 0
for element in a_list:
if element % 2 == 0:
b_list.pop(removal_key)
removal_key -= 1; # we need to push the removal key back afer every deletion as our array b_list becomes smaller than the original after every deletion
removal_key += 1
print(b_list) #[1, 3, 5]
If creating the 2nd list is not an option, you can store the key's of elements to be removed from the list and then use a second list to remove them :
a_list = [1, 2, 3, 4, 5]
elements_to_remove = []
for key, element in enumerate(a_list):
if element % 2 == 0:
elements_to_remove.append(key)
removed_emelent_count = 0
for element in elements_to_remove:
a_list.pop(element - removed_emelent_count)
removed_emelent_count += 1
print(a_list) #[1, 3, 5]
Note that the 1st solution is more time efficient (especially when removing a lot of elements) while the 2nd solution is more memory efficient, especially when removing smal number of elements from the list.
This is probably the case in which you should use generators.
A generator is a function that returns an object which we can iterate over, one value at a time, using the special keyword yield instead of return.
They allows you to have a smaller memory footprint, by keeping only one element per iteration.
In python3.x, range is actually a generator (python2.x is xrange).
Overly simple example:
>>> def range(start, end):
... current = start
... while current < end:
... yield current
... current += 1
...
>>> for i in range(0, 2):
... print(i)
...
0
1
How is this million entries list made?

delete element by deleting it from list

Let's say I have:
a = 1
b = 2
C = 'r'
my_list = [a,b,c]
Now let's say that a, b and c are unknown and I don't know their names.
If I do:
for x in my_list: del x
it doesn't work. a, b, c have not been deleted.
Can someone explain me why?
As #Coldspeed mentions in his comment, the variable x which you delete is not the same as the element in the list object.
Similar behaviour will be seen if you try to assign to x:
for x in my_list: x='bla' #does not modify anything in my_list
However, as the items are references to the same memory block, the comparison x is my_list[0] will equate to True in the first loop iteration.
As such, it is possible to perform operations on the list through usage of the shared reference, for example:
for x in my_list[:]: my_list.remove(x) #results in an empty list
Care has to be taken to first create a copy of the list and iterate over these items though, as was done in the previous lines. If you are hasty and loop over the items of a dynamically changing list, you will run into some more python magic.
for x in my_list: my_list.remove(x) #the first element gets deleted, then the second element in the list, which now has length 2, is deleted.
#Final result is the list [2] remaining
you have multiple issues here:
1. variable in list
a = 1
b = 2
my_list = [a,b]
assigns the values 1 and 2 to the list, not the vars. You can use mutable objects to get you desire: Immutable vs Mutable types
2. deleting a copy from a listvalue
for x in my_list:
del x
like in 1. x is just the value from the list (e.g. 1, 2, 'c'), but even worse, its a additional reference count to the memory.
Deleting it results in decreasing the counter, not deleting the value from memory, since at least one more counter is given by the original list (and in your case the vars (a,b,c) from the beginning).
More Info: Arguments are passed by assignment
3. deleting while iterating
for x in my_list:
del x
contains an other problem. If you would change the code to mylist.remove(x), to at least remove the entrie from the list, you would also skip every second member of the list. Quick Example:
li = [1,2,3]
for x in li:
li.remove(x)
first iteration would be x = 1. Deleting 1 from li results in li = [2,3]. Then the loop continous with the second position in the list: x=3 and deleting it. 2 was skipped.
This can be avoided by using a copy of the list using the [:] operator:
for x in li[:]:
li.remove(x)
This finaly results in an empty list

Why does a loop through a list of lists not recognise, that a list is a copy

I encountered a (in my opinion) extremely strange behavior, when looping through a list of lists. It is very difficult to explain, but here is an example code:
k = [[0],[1],[2]]
for lis in k:
lis_copy = lis
lis_copy.append(0)
print lis
When executing this, I get:
[0, 0]
[1, 0]
[2, 0]
This is very strange for me, as the list which is appended is a copy of lis,
but lis is appended as well. I would assume this list to be untouched.
For example doing the same with a list of integers the following happens:
k = [0,1,2]
for num in k:
num_copy = num
num_copy = 0
print num
Output:
0
1
2
Just as expected num is not touched by the manipulation of num_copy.
If somebody could explain why this is happening and how to avoid this,
like how to disconnect the lis_copy from is, that would be great.
Wow, I am amazed I did not encounter mayor problems before, without knowing this. I think I should review quiet some of my code. Anyway I thought this is somehow connected to the loop, this seems not to be the case, therefore I think the best explanation can be found here:
How to clone or copy a list?
This is because Python lists (and dicts) are not copied to a new list, but the new list becomes a reference to that list. if you truly want to copy the list, use deepcopy
You could use copy.copy() or copy.deepcopy()to avoid this behavior:
import copy
k = [[0],[1],[2]]
for lis in k:
lis_copy = copy.copy(lis)
lis_copy.append(0)
print lis
Output:
[0]
[1]
[2]
Source: https://docs.python.org/2/library/copy.html
Case a:
k = [[0],[1],[2]]
for lis in k:
lis_copy = lis
lis_copy.append(0)
print lis
We have a pointer to a list, and inside the loop we have another pointer made that points to the inner list objects. Then a zero is appended to each object.
Case b:
k = [0,1,2]
for num in k:
num_copy = num
num_copy = 0
print num
We have a pointer to a list, and inside the loop we have another pointer made that points to the inner integers. The difference is that in this case the pointer is changed to then point to a zero object rather than the list elements.

Python wont remove items from list

filtered_list = ['PerezHilton', 'tomCruise', 'q', 'p']
#BIO[user]['follows'] is just a list of strings say ['a', 'b', 'katieh']
#specs is also a string say eg. 'katieh'
for user in filtered_list:
if specs not in BIO[user]['follows']:
filtered_list.remove(user)
The above code for some reson gives this error "ValueError: list.remove(x): x not in list" but clearly 'p' is in the list so why is it not detecting 'p' but it is finding 'q'??
Im soo stumped but any help is appreciated, thanks
** SORRY i FIXED IT NOW *
The list comprehension that does this correctly in one line is at the bottom of the post. Here's some insight into the problem first.
Don't do things like:
for item in list_:
list_.remove(item)
because bad and confusing things happen.
>>> list_ = range(10)
>>> for item in list_:
... list_.remove(item)
...
>>> list_
[1, 3, 5, 7, 9]
Every time you remove an item, you change the indexes for the rest of the items which messes up the loop. One good way to remove items from a list while you're traversing it is to do it by index and work backwards so that removals don't affect the rest of the iterations. This is better because if you remove the 9'th element, then the 8'th element is still the 8'th element but the 10'th element becomes the 9'th element. If you've already dealt with that element, then you don't care what its index is.
>>> list_ = range(10)
>>> for i in xrange(len(list_) - 1, -1, -1):
... del list_[i]
...
>>> list_
[]
Or with a while loop:
i = len(list_)
while i:
i -= 1
del list_[i]
So in your case, the code would look something like
users[:] = [user for user in users if specs in BIO[user]['follows']]
because this is a filtering job and those are best done with list comprehensions. The point of the [:] is that it assigns to a slice of the list instead of clobbering the reference to the list. This means that every other reference to the list will be updated. It's essentially in-place, except that a copy is made before overwriting the original list. For the sake of completeness, here's how to do it with a while loop.
i = len(users)
while i:
i -= 1
if specs not in BIO[users[i]]['follows']:
del users[i]
You could do this if you wanted it done in place. No copy of the list is made here.
Why are you iterating?
>>> un = ['PerezHilton', 'tomCruise', 'q', 'p']
>>> un.remove('p')
>>> un
['PerezHilton', 'tomCruise', 'q']

Categories