3

I'm currently learning Python and I encountered an issue with with assignment to a list.

In

def nextPermutation(self, nums: List[int]) -> None:
    ...

I have a line of code that reverses the list as follows:

nums = nums[::-1]

but the test bench marks it as incorrect, whereas

nums[:] = nums[::-1]

is ok.

I suspect it's because I'm creating another nums list object and the original list that's passed in is unchanged, is that right?

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Harry
  • 111
  • 1
  • 8
  • 1
    Yes, that's right. – John Kugelman Jul 22 '19 at 19:43
  • 3
    `nums[::-1]` creates a new list. Assigning that to `nums` has no effect on the previous value of the variable. Assigning it to `nums[:]` stuffs it into the existing list, replacing all of the previous contents. – jasonharper Jul 22 '19 at 19:45

2 Answers2

13

If you know about C and pointers, you can picture nums as a pointer to a list.

Consider this case:

def reverse(nums):
    nums = nums[::-1]

my_list = [1, 2, 3]
reverse(my_list)

The assignment nums = nums[::-1] would be equivalent to create a new list in memory (with the elements reversed), and changing the pointer nums to point to that new list. But since the variable nums that you change is local to the function, this change does not affect to the external one passed as parameter, as shown in this picture:

bad

Now consider this case:

def reverse(nums):
   nums[:] = nums[::-1]

my_list = [1, 2, 3]
reverse(my_list)

The assignment nums[:] = nums[::-1] would be equivalent to write a new reversed list at the same address pointed by nums, because the slice allows to replace part of a list with new contents (although in this case the "part" is the whole list).

In this case you are not changing the variable nums, but the data pointed by it. This is shown in the following picture:

right

Note that in fact all variables in python are "pointer" in the sense used in this answer, but only lists and dicts allow to replace "in-place" some contents (they are mutable data types). With any other type (inmutable) you cannot alter the data in memory, so all you can do is to "reassign the pointer" as in the first case, and this is the reason why a function cannot change the data received as parameter, except for the list case.

JLDiaz
  • 1,248
  • 9
  • 21
  • 1
    Just curious, what program/website did you use to create these diagrams? (If it's not a secret) Looks nice, so upvoted... – Andrej Kesely Jul 22 '19 at 20:20
  • 4
    @AndrejKesely Haha! It is not a secret. I used https://www.draw.io, which is amazing, free, and online – JLDiaz Jul 22 '19 at 20:22
  • Great stuff! This answer should also go into the dup in my opinion, the charts are really helpful. – Adam.Er8 Jul 23 '19 at 05:36
6

your assumption is exactly right.

we can verify this by using the builtin function id to get the address of the object in memory

try to run this:

def func(nums):
    print(id(nums),"before change")
    nums[:] = nums[::-1]
    print(id(nums), "after [:]")
    nums = nums[::-1]
    print(id(nums), "after regular assign")

a = [1,2,3]
print(id(a),"original")
func(a)

Output (numbers may vary):

55313512 original
55313512 before change
55313512 after [:]
55297688 after regular assign

as you can see, the last id returns a different value, while the rest (from within and from outside of the function) return the same one.

Adam.Er8
  • 12,675
  • 3
  • 26
  • 38