What ways can we use to remove elements from a list in Python?

in a task I was trying to delete first numbers of a list using .pop(0), when the list only have one element, the function didn’t work properly, why?

>>> [1].pop(0)
1
>>> a = [1]
>>> a.pop(0)
1
>>> a
[]
>>> def pop_zero(a):
	return a.pop(0)

>>> pop_zero([1])
1
>>> 

When you say it doesn’t work, what are you getting for a result? Please post your code.

Actually, that does not remove items from list. Rather, it creates a new list that excludes the first two items of the original list and assigns it to list. The original list is unaltered.
The distinction between altering a list and creating a new list is significant. Observe the behavior of the following code, and refer to the included comments:

list_a = [1, 2, 3, 4, 5, 6, 7]
list_b = list_a
# Now list_a and list_b refer to the same list
print(list_a)
print(list_b)
# The following alters list_a and list_b, since they refer to the same list
list_a.append(8)
print(list_a)
print(list_b)
# Assign a new list to list_a that excludes the first two items
list_a = list_a[2:]
# Now list_a and list_b refer to different lists
print(list_a)
print(list_b)

Output:

[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
1 Like
>>> a = list('abcdefghijklmnopqrstuvwxyz')
>>> def foo(x):
    bar = x[:]
    del(x[7:17])
    return bar

>>> foo(a)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> a
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> 
Silliness
>>> def foo(x):
    bar = x[:]
    n = len(x) // 2
    del(x[n-1:n+1])
    return bar

>>> a = list('abcdefghijklmnopqrstuvwxyz')
>>> foo(a)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> a
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> foo(a)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> a
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> foo(a)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> a
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> foo(a)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> a
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> foo(a)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> a
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> foo(a)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> a
['a', 'b', 'c', 'd', 'e', 'f', 'g', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> foo(a)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> a
['a', 'b', 'c', 'd', 'e', 'f', 'u', 'v', 'w', 'x', 'y', 'z']
>>> foo(a)
['a', 'b', 'c', 'd', 'e', 'f', 'u', 'v', 'w', 'x', 'y', 'z']
>>> a
['a', 'b', 'c', 'd', 'e', 'v', 'w', 'x', 'y', 'z']
>>> foo(a)
['a', 'b', 'c', 'd', 'e', 'v', 'w', 'x', 'y', 'z']
>>> a
['a', 'b', 'c', 'd', 'w', 'x', 'y', 'z']
>>> foo(a)
['a', 'b', 'c', 'd', 'w', 'x', 'y', 'z']
>>> a
['a', 'b', 'c', 'x', 'y', 'z']
>>> foo(a)
['a', 'b', 'c', 'x', 'y', 'z']
>>> a
['a', 'b', 'y', 'z']
>>> foo(a)
['a', 'b', 'y', 'z']
>>> a
['a', 'z']
>>> foo(a)
['a', 'z']
>>> a
[]
>>> 
1 Like

Cool. :sunglasses:

The following demonstrates how we can use slicing to remove, add, and replace portions of a list. They alter the original list rather than replace it with a new list.

trees = ["white oak",
         "tuliptree",
         "blue spruce",
         "tamarack",
         "paper birch",
         "honey locust",
         "basswood"]

# remove items at indexes 3, 4, 5 by replacing slice with empty list
trees[3:6] = []
print(trees)
# add items starting at index 1 by replacing an empty slice with non-empty list
trees[1:1] = ["pawpaw", "river birch"]
print(trees)
# remove and add items at the same time by replacing a non-empty slice with non-empty list
trees[2:4] = ["giant sequoia", "ginkgo", "metasequoia"]
print(trees)
# duplicate a portion of the list
trees[3:3] = trees[3:5]
print(trees)
# remove all items from the list
trees[:] = []
print(trees)

Output:

['white oak', 'tuliptree', 'blue spruce', 'basswood']
['white oak', 'pawpaw', 'river birch', 'tuliptree', 'blue spruce', 'basswood']
['white oak', 'pawpaw', 'giant sequoia', 'ginkgo', 'metasequoia', 'blue spruce', 'basswood']
['white oak', 'pawpaw', 'giant sequoia', 'ginkgo', 'metasequoia', 'ginkgo', 'metasequoia', 'blue spruce', 'basswood']
[]
3 Likes

I’ve bookmarked your post and given it some thought. Can’t say this is derivative, but it stems from the root…

>>> a = list('abcdefghijklmnopqrstuvwxyz')
>>> a[7:17] == a[-19:-9]
True
>>> 

Most of those trees may exist where you are, but in our parts I feel joy whenever I see a river birch. The rest are not native to our region. We have slightly less rare stands of cottonwood, but mostly its aspen, willow, wild-berry (prickly rose, chokecherry, saskatoon). There are no ‘great trees’ on the prairies.

digger_in_the_valley

During the railroad era of a century ago they introduced spruce from the Jasper region, so we have lots of them around, but all human planted.

34827_1379143457716_1122045_n

All those evergreens were put there by humans.

Not so easy to see, now, though,

34447_1220991864025_3611506_n

3 Likes

Yes, this is the form that .pop() works, but i used something like that.

lst = […]
For n in range(len(lst)):
If lst[n] > 3:
lst.pop(n)
print(lst)

And in this case, when only was one element in the list and this element was grater than 3, the function didn’t delete the element from the list. I didn’t print the pop of the list (print(lst.pop(index))):sweat_smile:

Ultimately, as list methods go, that is one of the key roles that pop serves… It gives us the value it just removed. If the value is of no importance, then use another method, would be my leaning.

The del function is quick and ideal. It removes the slice of values and Bob’s yer uncle.

list.remove is also an option, but it looks for values, not indices.

Okay, now let’s get down to understanding your code.

We are given a list of some length. Now we are to iterate a range the length of the list. That will give us indices with which to poll the list. If the value at that index is greater than three then pop that index. Print the list at that point (or at the end, can’t tell).

Here’s the problem… When we remove an element from a list, it gets shortened by one. The range we’re iterating over doesn’t, so, at some point we will reach an index that is out or range since the list is shorter and that index no longer exists.

2 Likes

I first solved the exercises by creating a slice with just the remaining entries, which worked fine. I then thought I’d give the other possible ways to do it a try, i.e. .remove(), .pop(), del.

I really struggle with how .remove() and pop() works as it doesn’t do what one would think:

lst = [1, 2, 3, 4]
new_lst = lst

new_lst.remove(3) # this should look up the value 3 in new_list and remove it

print(new_list) # [1, 2, 4]
print(lst) # [1, 2, 4]

I don’t quite understand why it is removing the entry also from lst. How could I create a copy of the original list that I can then manipulate to create a new subset of the original list without changing the original list?

Here are three ways. Uncomment each in turn to try them out.

lst = [1, 2, 3, 4]
#new_lst = lst[:]
#new_lst = lst.copy()
new_lst = list(lst)

new_lst.remove(3) # this should look up the value 3 in new_list and remove it

print(new_lst) 
print(lst) 

Output (for any of the three options shown):

[1, 2, 4]
[1, 2, 3, 4]

If your original list contains mutable objects (i.e., lists), and you experimant a bit, you will discover some interesting, possibly unexpected behavior. To get around it, you will need to use copy.deepcopy:

lst = [1, [2, 3, 4]]
new_lst = lst[:]

print(new_lst[1])    # sublist
new_lst[1].remove(3) # this should look up the value 3 in new_list and remove it

print(new_lst) # [1, 2, 4]
print(lst) # [1, 2, 4]

print("Now using deepcopy")
import copy
lst = [1, [2, 3, 4]]
new_lst = copy.deepcopy(lst)

print(new_lst[1])
new_lst[1].remove(3) # this should look up the value 3 in new_list and remove it

print(new_lst) # [1, 2, 4]
print(lst) # [1, 2, 4]

Output:

[2, 3, 4]  # here is the sublist
[1, [2, 4]]  
[1, [2, 4]]     # original list affected
Now using deepcopy
[2, 3, 4]
[1, [2, 4]]
[1, [2, 3, 4]]  # original list unaffected
1 Like

Thanks you very much for the answer.

I now understand that there does seem to be a difference in how matlab or VBA handles new matrices (lists) and Python. In matlab if I create a new matrix and assign an old one to it, I have 2 identical matrices that I can manipulate independently.

I am now trying to understand conceptually what python is exactly doing. So in my example, did I tell python to basically save a “link” (bookmark) to the other list instead of the list?

lst = [1, 2, 3, 4]
new_lst = lst

If so, what is happening in your example? The first “layer” seems to be copied as a copy (one can delete 1 without deleting it in the original list). But the second “layer” is a “link”?

Are there any good sources I could consult to learn how python organises its data? I now know what to do to get the desired result, but I would like to better understand why.

Phillip, you have very nicely summarized all three situations

  1. alias: new_lst = my_lst
  2. “shallow” copy (top layer only): new_lst = copy(my_lst) (& other methods, as above.)
  3. “deep” copy (separate copy all the way down): new_lst = copy.deepcopy(my_lst)

If you Google “Python list copying” you get about 8.5 million hits; here is one that summarizes things nicely

1 Like

Addendum with respect to copying objects

When we want to be sure that our copy is indeed a copy, and not a reference to the original. check its id.

>>> p = [1, 2, 3]
>>> id(p)
140669632471176
>>> q = p
> id(q)
140669632471176
>>> 

If the ids match, it’s not a copy, but a reference, only.

>>> id(199)
140669688098336
>>> x = 199
>>> id(x)
140669688098336
>>> 

Above we can see how Python will give a number value an id. There is only ever one value in memory of any one number, never more. When we assign that number to a variable, it still has the same id.

That will be the case no matter what structure we use the number in.

>>> u = [1, 9, 19, 99, 199]
>>> id(u[4])
140669688098336
>>> v = {'one': 1, 'one-ninety-nine': 199}
>>> id(v['one-ninety-nine'])
140669688098336
>>>

As for copies, that is true copies, or clones,

>>> id(u)
140669625218312
>>> w = u.copy()
>>> id(w)
140669625233288
>>> id(w[4])
140669688098336
>>> 

Note the last id. Granted the two lists are unique, now, but any values they have in common ARE the same value in memory.

This parallels through all objects, regardless their data type. There is only ever one of any one value in memory.

1 Like

Thank you, for taking the time to respond. This is very helpful. The link also cleared a couple of things up for me.

@mtf: Thank you very much as well. I think the id() check will be very useful going forward.

2 Likes

Hi guys.
What do you think about my way to solve this task? It’s quite different than the original solution what we get by pressing the solution button.

delete%20starting%20even%20numbers

When working with lists we always need to consider whether we wish to intentionally mutate or possibly destroy the original list, as has been discussed. Can we do this without affecting the original?

Yes, we can…

>>> def delete_starting_evens(lst):
	for i, x in enumerate(lst):
		if x % 2:
			return lst[i:]
	return []

>>> lst = [2,4,6,7,6,5,4,3]
>>> delete_starting_evens(lst)
[7, 6, 5, 4, 3]
>>> delete_starting_evens([])
[]
>>> delete_starting_evens([2,4,6,8,10])
[]
>>> lst
[2, 4, 6, 7, 6, 5, 4, 3]
>>> 

Direct inputs don’t matter since they only exist in the argument, not somewhere in memory. A list that exists in memory is mutable by the function we pass it into. How we manipulate the list inside the function has a direct effect on the list in memory.

Bottom line, if it is not our clear intention to mutate an existing data structure, then special care must be taken to either not alter the list, or to create a spin-off list of only the data we want and return that; or, like above, don’t alter the list in any way and only return the portion (slice) of the list that suits our purpose.

I don’t quite grasp how the provided solution function works (see solution), specifically with lst = lst[1:]

If a list parameter were to consist of only 1 number–as becomes any list consisting of only even numbers as it is processed by the loop function–which would have an index of 0, how does using the slicing language lst[1:] function properly in this case?

2 Likes

i think the “for i in lst” statement makes the program iterate through every component and len(lst)>0
condition makes sure before the loop starts to run both of the conditions are met; so if lst has only one component , it will satisfy the condition of len(lst)>0 beside the modulo operator condiditon and while loop will run.

1 Like

i didn’t get how the duplication part works

The following addresses that and related issues.

Let’s consider the difference between identity and equivalence in Python.

In Python, objects are identical if they are, in fact, the same object.

Objects are considered to be equivalent if they have the same value. There are some minor exceptions that recognize equivalence of values that are similar, but not of the same type. See the end of this post for an example.

If objects are identical, they are also equivalent. However, if they are equivalent, they are not necessarily also identical. The following may help clarify the difference between identity and equivalence regarding Python lists.

To help with the discussion, we will be using the following operators:

  • is: tests for identity
  • ==: tests for equivalence

Let’s create two lists with the same content. They will be equivalent, but not identical.

>>> a = [1, 2, 3, 4, 5, 6, 7]
>>> b = [1, 2, 3, 4, 5, 6, 7]
>>> a == b # are they equivalent?
True
>>> a is b # are they identical?
False

With lists, the = operator assigns a reference to the list specified by the expression on the right side of the operator to a variable on the left side of the operator. Let’s demonstrate that by creating a list, assigning it to c, then assigning c to d. This will create only one list, Both c and d will refer to that same list. Therefore, c and d will be identical and also equivalent.

>>> c = [1, 2, 3, 4, 5, 6, 7]
>>> d = c
>>> c == d
True
>>> c is d
True

Taking a slice of a list creates a new list. We can use this as a means of making a copy of a list. Let’s try that with two variables, e, and f.

>>> e = [1, 2, 3, 4, 5, 6, 7]
>>> f = e[:] # this slice includes all of the elements in e
>>> e == f
True
>>> e is f
False

Recall that a and b are equivalent, but not identical. Let’s modify a and find out whether that affects b.

>>> a.append(8) # 8 gets appended to a, but not to b
>>> a
[1, 2, 3, 4, 5, 6, 7, 8]
>>> b
[1, 2, 3, 4, 5, 6, 7]

b was not affected.

Recall that c and d are identical. Let’s modify c and find out whether that affects d.

>>> c.append(8) # 8 gets appended to c, which affects d
>>> c
[1, 2, 3, 4, 5, 6, 7, 8]
>>> d
[1, 2, 3, 4, 5, 6, 7, 8]

Modifying c modified d, because both variables refer to the same list.

When a list is passed to a function as an argument, the corresponding function parameter will refer to the same list as the argument. If we wish to work with a copy of the list instead of with the original, we must make a copy of the list. Within the above examples, we used a slice to assign a copy of e to f. Alternatively, we could have used the list function to create the copy, as follows:

>>> f = list(e)
>>> e == f
True
>>> e is f
False

As promised above, here is an equivalence between lists that contain items that differ in type:

>>> [1.0, 0.0, 1.0] == [True, False, True]
True

Edited on September 6, 2019 to modify the final example