-1

I am attempting a variant of this simple function:

def reverse_this(thisArray):
    
    saved_first = thisArray[0]
    thisArray = thisArray[1:]
    thisArray.reverse()
    thisArray.insert(0,saved_first)

myList = ['foo', 1,2,3,4,5]
reverse_this(myList)
print('after:', myList)

In essence, I want it to print out "after: ['foo', 5, 4, 3, 2, 1]". However, the slicing here seems to be causing some issues. It appears that the list is being modified as intended inside the function, however, the changes are not being applied outside the function to myList. What's going on here and how can I fix it?

3 Answers3

3

Python is pass by assignment for passing arguments inside the function. When you do

thisArray = thisArray[1:]

inside your function, you re-bind the passed argument to a new object created inside your function and proceed to mutate that copy created after slicing instead, rather than modifying your argument thisArray. To get the modified value, one way is to return this modified list from your function:

def reverse_this(thisArray):
    saved_first = thisArray[0]
    thisArray = thisArray[1:]
    thisArray.reverse()
    thisArray.insert(0,saved_first)
    return thisArray

myList = ['foo', 1,2,3,4,5]
myList = reverse_this(myList)
print('after:', myList)

Other way is to modify the list's contents (assign to the slice) rather than re-binding the list itself:

def reverse_this(thisArray):
    saved_first = thisArray[0]
    thisArray[:] = thisArray[1:] # assign to the sliced list's contents
    thisArray.reverse()
    thisArray.insert(0,saved_first)
Jarvis
  • 8,494
  • 3
  • 27
  • 58
2

thisArray is re-assigned to a new list object by slicing, so any changes after that don't affect the original list passed into the function. Note the instance IDs of the lists:

def reverse_this(thisArray):
    
    saved_first = thisArray[0]
    print(f'thisArray pre-slice:  {id(thisArray):#x}')
    thisArray = thisArray[1:]
    print(f'thisArray post-slice: {id(thisArray):#x}')
    thisArray.reverse()
    thisArray.insert(0,saved_first)

myList = ['foo', 1,2,3,4,5]
print(f'myList pre-call:      {id(myList):#x}')
reverse_this(myList)
print(f'myList post-call:     {id(myList):#x}')
print('after:', myList)
myList pre-call:      0x1e2f5713500
thisArray pre-slice:  0x1e2f5713500  # parameter refers to original list
thisArray post-slice: 0x1e2f5753800  # thisArray refers to new list
myList post-call:     0x1e2f5713500  # original list isn't changed.
after: ['foo', 1, 2, 3, 4, 5]

In Python, variables are names of objects. If you mutate an object, all names of that object "see" the change. A slice makes a new object, and in this case the name was reassigned to the new object.

To fix it, assign the slice into the full range of the original list, which mutates the original list instead of creating a new one:

def reverse_this(thisArray):
    
    saved_first = thisArray[0]
    thisArray[:] = thisArray[1:]   # replace entire content of original list with slice.
    thisArray.reverse()            # thisArray still refers to original list here.
    thisArray.insert(0,saved_first)

myList = ['foo', 1,2,3,4,5]
reverse_this(myList)
print('after:', myList)
after: ['foo', 5, 4, 3, 2, 1]

When assigning to a slice, a section of a list is replaced in place with another list. It doesn't necessarily have to be the same size.

Another example:

>>> s = list(range(10))
>>> s
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> id(s)
2339680033664
>>> s[4:7] = [10,10]           # replace indices 4 up to but not including 7
>>> s
[0, 1, 2, 3, 10, 10, 7, 8, 9]
>>> id(s)                           # id didn't change
2339680033664
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
1

You can do the following to achieve the same thing:

def my_reverse(lst):
    lst[1:] = reversed(lst[1:]) # Or lst[:0:-1]

Assigning to a list slice does not create any copies it simply replaces that slice. See this.

ssp
  • 1,666
  • 11
  • 15