Hướng dẫn list mutation python

The mutable and immutable datatypes in Python cause a lot of headache for new programmers. In simple words, mutable means ‘able to be changed’ and immutable means ‘constant’. Want your head to spin? Consider this example:

foo = ['hi']
print(foo)
# Output: ['hi']

bar = foo
bar += ['bye']
print(foo)
# Output: ['hi', 'bye']

What just happened? We were not expecting that! We were expecting something like this:

foo = ['hi']
print(foo)
# Output: ['hi']

bar = foo
bar += ['bye']

print(foo)
# Expected Output: ['hi']
# Output: ['hi', 'bye']

print(bar)
# Output: ['hi', 'bye']

It’s not a bug. It’s mutability in action. Whenever you assign a variable to another variable of mutable datatype, any changes to the data are reflected by both variables. The new variable is just an alias for the old variable. This is only true for mutable datatypes. Here is a gotcha involving functions and mutable data types:

def add_to(num, target=[]):
    target.append(num)
    return target

add_to(1)
# Output: [1]

add_to(2)
# Output: [1, 2]

add_to(3)
# Output: [1, 2, 3]

You might have expected it to behave differently. You might be expecting that a fresh list would be created when you call add_to like this:

def add_to(num, target=[]):
    target.append(num)
    return target

add_to(1)
# Output: [1]

add_to(2)
# Output: [2]

add_to(3)
# Output: [3]

Well again it is the mutability of lists which causes this pain. In Python the default arguments are evaluated once when the function is defined, not each time the function is called. You should never define default arguments of mutable type unless you know what you are doing. You should do something like this:

def add_to(element, target=None):
    if target is None:
        target = []
    target.append(element)
    return target

Now whenever you call the function without the target argument, a new list is created. For instance:

add_to(42)
# Output: [42]

add_to(42)
# Output: [42]

add_to(42)
# Output: [42]

def mutation(input_list):
  list_copy = input_list[:]
  list_copy[0] = 10
  input_list = list_copy

# Correctly mutates
sample_list = [0,1,2]
sample_copy = sample_list[:]
sample_copy[0] = 10
sample_list = sample_copy
print(sample_list)

# Incorrectly mutates
sample_list = [0,1,2]
mutation(sample_list)
print(sample_list)

In the top snippet of code, I've made a copy of a list and modified it. I then set the original to the copy and then it works. What confuses me is why doing this process outside of a function works but if I were to do it inside a function (the 2nd snippet of code), it fails?

For reference, the code returns:

[10, 1, 2]
[0, 1, 2]

EDIT: I know that calling input_list[0] = 10 works. I just want to know what makes this different from what I showed above all in memory?

asked Nov 5, 2017 at 0:09

Jaden WangJaden Wang

1261 silver badge9 bronze badges

4

In mutation, input_list starts out pointing at the same object as sample_list, but later you make it point at list_copy. sample_list is not modified. It is still pointing at the original object.

When you do it outside of the function you change sample_list to point to the new object before printing it.

answered Nov 5, 2017 at 0:18

Hướng dẫn list mutation python

WodinWodin

3,1321 gold badge25 silver badges55 bronze badges

I think that using the built-in function id to show the object ID will help here. If the ID of two variable names gives the same result then they refer to the same object; otherwise the objects are different.

>>> def mutation(input_list):
...     print(id(input_list))
...     list_copy = input_list[:]
...     print(id(list_copy))
...     input_list = list_copy
...     print(id(input_list))
...     
>>> a = list(range(10))
>>> print(id(a))
140737233394376
>>> mutation(a)
140737233394376
140737233289160
140737233289160

In the above, we see that after input_list = list_copy, the name input_list refers to identically the same object in memory as list_copy, which means it no longer refers to the list given as the function argument. This is why the mutation you expect does not work - you are modifying an entirely different object.

answered Nov 5, 2017 at 0:28

SethMMortonSethMMorton

42.4k12 gold badges64 silver badges80 bronze badges

That's because you sets new value for input_list which is local variable of mutation function.

The simplest solution is changing value of first element of list passed as argument:

def mutation(input_list):
    input_list[0] = 10

Otherwise you can write function which changes value of global variable called sample_list

def mutation():
    global sample_list
    list_copy = sample_list[:]
    list_copy[0] = 10
    sample_list = list_copy

answered Nov 5, 2017 at 0:15

Hướng dẫn list mutation python

domandinhodomandinho

1,2422 gold badges14 silver badges28 bronze badges

0