0

This one is making me absolutely crazy, so any help would be much appreciated. I have a program where I'm iterating through a list in a function. Here's a toy model of the problem:

masterList = ["person","woman","man","camera","television"]
workingList = masterList

def removeItem (workingList):
    item = workingList.pop(2)
    print("Test 1:",workingList)


removeItem(workingList)

print("Test 2:", workingList)
print("Test 3:", masterList)

As expected, "Test 1" prints out the list with an item removed.
However, "Test 2" also prints out the list with an item removed. I wasn't expecting that, but no matter. That's not the real problem, but I'm sure there's something I don't understand here about variable scopes and variable shadowing.

No the real problem is "Test 3", which as you can see, is printing out the masterList, which shouldn't even be touched by the removeItem function or the pop function within it. And yet, it too is printing out the list with an item removed.

How can this be happening, and how can I prevent it?

Thanks so much!

Cheers, Ari

A Levinson
  • 42
  • 5

3 Answers3

3

Python lists are mutable objects.

m = list([1, 2, 3])
n = m

a = 5
b = a

id(a) == id(b)
# id() return "identity" of the object.
# True,  Both a and b are references to the same object. 
id(m) == id(n)
# True,  Both m and n are references to the same object. 

b = b + 2 
id(a) == id(b)
# False, a new object on separate location is created, which b will point.

n.pop()
id(m) == id(n)
# True, the object is mutated, and m and n still references to the same object.

Since, python lists are mutable, m and n will still be reference to the same object after mutation. Whereas, for immutable objects like int, a new object will be created and the identifier will refer to the new object.

Gist is, in your scenario, there has been only one object since python lists are mutable.

However, if you need the original list unchanged when the new list is modified, you can use the copy() method.

new_list = original_list.copy()

The ids of new_list and original_list is different.

Learn here about mutability in detail: https://medium.com/@meghamohan/mutable-and-immutable-side-of-python-c2145cf72747.

Sagar Adhikari
  • 1,312
  • 1
  • 10
  • 17
2

You have to make a copy of the masterList, otherwise workingList is nothing more than a reference.

If your list does not contain other containers (which yours doesn't), then a shallow copy is sufficient. There are numerous ways to make a shallow copy but slicing is the most optimal.

masterList = ["person","woman","man","camera","television"]
workingList = masterList[:] #copy via slice

a bunch of other ways to make a shallow copy

workingList = masterList * 1
workingList = masterList.copy()
workingList = list(masterList)
workingList = [*masterList]

import copy
workingList = copy.copy(masterList)

If you have a list that does possess something that holds a reference (like other containers, classes, etc), then you need to make a deepcopy.

import copy
a = [[1, 2, 3], ['a', 'b', 'c']]
b = copy.deepcopy(a)
OneMadGypsy
  • 4,640
  • 3
  • 10
  • 26
1

Looks like I figured it out. The two lists are actually the same list unless you use list.copy()

So replacing the top two lines with:

masterList = ["person","woman","man","camera","television"]
workingList = masterList.copy()

makes everything perform as expected! Well, I still can't say I understand how the variable scopes work in their entirety, but at least this solves the major problem.

A Levinson
  • 42
  • 5