0

I have a question about how to properly construct functions in python that have side effects.

Let's say I have some code like this, that is supposed to remove occurrences of a number from a list:

def removeNumber(nums, val):
    nums = [num for num in nums if num != val]

If i then use this code from outside the function like so:

my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
removeNumber(my_list, 4)

print(my_list)

my_list remains unchanged, since nums is only a slot in memory that used to point to the same list as my_list, but that after my new assignment points to a new list that looks the way I'd like.

How would I go about changing my_list to point to the new list I have just created?

I know that referencing nums[n] will go to the actual list in memory, but I find that to be a bit clunky. Thanks!

  • You care creating a new list. If you simply do nums.remove(val), you will have the original one. You can read this for more info: https://nedbatchelder.com/text/names.html – lllrnr101 Apr 19 '21 at 07:10
  • You must *use some mutator method on the list object to mutate it*. "variables" are not passed in Python, objects are. Python does not support call by reference (thankfully) – juanpa.arrivillaga Apr 19 '21 at 07:17
  • Also, Python variables are not "slots in memory". Python variables are names in namespaces that refer to objects. How that is implemented doesn't matter. – juanpa.arrivillaga Apr 19 '21 at 07:18

2 Answers2

3

Try this:

def removeNumber(nums, val):
    nums[:] = [num for num in nums if num != val]

my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
removeNumber(my_list, 4)

print(my_list)

This way you modify the list elements, not the list itself.

Explanation

Assuming you have some basic knowledge about Python objects, you basically create a second reference to the list when calling the function (first reference is mylist). So nums points to the same object. When you do it like you originally did, you let nums point to the new expression, e.g. your list comprehension. So the original list is left untouched.

When you use the [:] syntax, both pointers still point to the same object, but you modify the elements of the list, not the list itself. You can experiment on this a little bit by looking at the object IDs.

DocDriven
  • 3,726
  • 6
  • 24
  • 53
  • This is it, thanks! Would you mind explaining why it works? – Gustaf Engström Apr 19 '21 at 11:25
  • @GustafEngström I modfied my answer. Also, this might help: https://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference – DocDriven Apr 19 '21 at 12:21
  • Hey thanks a lot for taking the time! I would like to ask you again though for an explanation of your solution in particular. What is it that makes that splice operator guide the assignment towards the original list. I would imagine it's used to make a shallow copy here, but I don't quite see why that would have this effect either. – Gustaf Engström Apr 19 '21 at 16:13
  • @GustafEngström Glad I can help. Regarding your question: the slice operator on the left side does NOT create a shallow copy. One way to create a shallow copy is ``a = b[:]`` given the iterable ``b``. What you are actually doing is assigning values to a list slice which happens to be the entire list and thus effectively replacing the contents with your list comprehension. It is saving you the pain of modifying each list entry separately. – DocDriven Apr 19 '21 at 17:40
1

If I understood your question correctly,

def removeNumber(nums, val):
    nums = [num for num in nums if num != val]
    return nums

my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
my_list = removeNumber(my_list, 4)

Just return your new list from the function and reassign your list with it.

fordev
  • 65
  • 7