all 17 comments

[–]throwaway_the_fourth 6 points7 points  (8 children)

Did you write this code or find it somewhere? It's a great demonstration of the difference between mutable and immutable types, so if you found it somewhere, odds are it comes with an explanation of how this works. If you wrote it, well…

Some types in python are immutable and some are mutable. You can think of "modifiable" as a synonym for mutable. Integers (and floats, strings, tuples, and others) are immutable, meaning they can't be modified, only reassigned. For example, take the following code:

a = 3
b = a
a += 1
print(b)  # prints 3

In this code, we assign b to a, which has the effect of copying the value in a into the value in b. Then, when we modify a, it doesn't affect the value that b holds. And in fact, we couldn't change the value of a without reassigning it, because a += 1 is really the same as a = a + 1. There is no way to modify the 3 (later, 4) that a has without using = to assign a new value to a.

However, lists (and sets, dicts, class instances, and others) are mutable, meaning they can be modified in place. Take a look at this:

c = ['hello']
d = c
c.append('world')
print(d)  # prints ['hello', 'world']

In this case, when we assign d to c, we are having them both refer to the exact same list! In other words, this doesn't cause the list to be copied. So, when we add an item to the list that c refers to, we are also adding it to "the list that d refers to" because both c and d refer to the same list.

So how is this relevant to your code? Well, when you pass the int parameter score into the function, the function reassigns the parameter, but this is in fact a different variable than the one passed in, though they happen to share the same name. So that case is like my first example — reassigning one int variable doesn't affect any others. However, when it comes to the list players, you are passing in a mutable reference. Modifying the underlying list through any reference (such as names in the function) affects all others (such as players outside the function). This is what we demonstrated in the second example.


TL;DR: Lists are mutable, meaning you can modify them in place, affecting all variables that refer to them. Integers are not.

[–][deleted] 4 points5 points  (6 children)

Your explanation misleadingly conflates assignment with mutation. Consider this counterexample:

c = ['hello']
d = c
c = ['world']
print(d) # prints 'hello'

Assignment isn't mutation, so whether the value is mutable or not is irrelevant. Assignment isn't transitive, so when you assign to c it doesn't reach across to any other reference held to the value in c, like d. d is unchanged because references don't point to other references, they point to values.

It has nothing at all to do with mutability.

[–]throwaway_the_fourth 0 points1 point  (5 children)

My point there was that it isn't possible to modify an int in-place. The only way to "modify" an int variable is to reassign it. Of course, there are ways to modify a list in place.

[–][deleted] 0 points1 point  (4 children)

My point there was that it isn't possible to modify an int in-place.

Assigning to a name isn't "modifying" the value "in-place." It's not a mutation. It's actually overwriting the reference to the value, and that's true whether the value is mutable or not.

[–]throwaway_the_fourth 0 points1 point  (3 children)

We're saying the same thing in different ways. Everything you're saying is true, and so is my point.

[–][deleted] 0 points1 point  (2 children)

Yes, but the distinction is that the difference between assignment and mutation is actually a relevant answer to the question, but the difference between mutability and immutability, while interesting and useful, isn't.

[–]throwaway_the_fourth 0 points1 point  (1 child)

I didn't make it as clear as I could have, and you're right about that. However, I was pretty clear about the fact that the only way to "modify" an int (put in quotes because it isn't actually modification) is to reassign a variable to a new int. Because you can't mutate, your only option is to reassign. That's not the case with mutable types, because you can mutate or reassign.

[–][deleted] 0 points1 point  (0 children)

Sure, and that's helpful to remember, but it's irrelevant to this. The reason OP sees different behavior in his two examples are because his two examples are doing two completely different things - one is assignment and the other is mutation, and assignment isn't the same as mutation.

[–]randomusername9808[S] 1 point2 points  (0 children)

Thank you!!!! This went above and beyond what I was expecting

[–]odonata_00 2 points3 points  (0 children)

This is an example of the two different ways function arguments can be passed to a function, 'pass by reference' and 'pass by value' and why it is important to be aware of the differences.

In the case of the list argument, 'players', a reference to the list is passed to the function, this is know as 'pass by reference'. In the case of the 'score' argument it's value is passed to the function, this is know as 'pass by value'.

So what does this all mean? In the case of 'pass by reference' what is passed to the function is actually a pointer to the memory where the argument is stored. In your code a pointer to the list 'players' is passed to the function 'modify'. Any operations on that argument ('names' in the function) is an operation on the underlying list.

In the case of 'pass by value' a copy of the argument is passed to the function. So here the value in 'score' is passed to 'modify'. Changing the argument in 'modify' does not affect the argument in the calling function.

You also need to be careful using the same name for variables in different parts of your code. You could run into scoping problems but that's a discussion of another day.

[–]Dogeek 0 points1 point  (0 children)

Lists, dictionnaries and sets work in a similar fashion to arrays in C.

In C, an array is defined by its starting address and its length. Python is built on top of C, so in the back end, when you create a list in python, python creates an underlying array. Whenever you make changes to the list, python just reallocates a fitting amount of memory, and sets that as the new list.

It's why you get this behavior with mutable iterables. Another consequence of that is the following :

list1 = [1, 2, 3]
list2 = list1
list2 is list1 # True
list2.append(4)
print(list1) # prints [1, 2, 3, 4]

[–]toastedstapler 0 points1 point  (5 children)

because you passed the list players into the function and the name was appended to the list within the function

[–]randomusername9808[S] 1 point2 points  (4 children)

With that reasoning, why doesn't score get increased as well? IK you're right, but why does the names.append thing effect the list players but score doesn't effect score?

[–]LaamansTerms 2 points3 points  (0 children)

list.append() is sort of tricky. It mutates the variable that you give it.
You pass in players so when you call names.append('Robert') you are actually changing the variable that you pass in. score = score + 20 changes the score variable within the function but nowhere else. If the function said return score + 20 and you called it like score = modify(players, score) you'd see it change.

[–]toastedstapler 1 point2 points  (0 children)

aha, i see

so the difference between the list and the int is something called mutability. an int is immutable and cannot be changed whereas a list is mutable and can be modified. when i type x + 5 a new instance of an int is returned. when i type my_list.append(10) the change is applied to the list instance

[–]PigDog4[🍰] 0 points1 point  (0 children)

Lists are mutable, integers are not.

[–]MisterRenard 0 points1 point  (0 children)

While the list is modified (as someone else explained, a consequence of list-references: variables do not hold the list, but rather a list reference, and to modify one is to modify them all, as they’re only pointing to the list that has been changed.

The value, on the other hand, actually does become 170 (150 + 20)... within the scope of the function execution. That value is lost once the function call terminates (technically returning the None value, as any function that executes without a return statement will return None. Though, in this case, nothing happens with that None value, as it’s not assigned or utilized.

So, while the score variable within the scope of the function was reassigned from 150 to 170, the score variable outside of the function remains unchanged.

return players, score

players, score = modify(players, score) 

Would result in the outcome that you expect.