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!

3
  • 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: nedbatchelder.com/text/names.html Commented Apr 19, 2021 at 7: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) Commented Apr 19, 2021 at 7: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. Commented Apr 19, 2021 at 7:18

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

This is it, thanks! Would you mind explaining why it works?
@GustafEngström I modfied my answer. Also, this might help: stackoverflow.com/questions/986006/…
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.
@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.
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.

1 Comment

Thanks, but no that's not quite it, since that would remove the side/effect part of the problem

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.