In Python, most arguments are taken by reference.
Your function, shift_to_left
, actually mutates its argument (through the use of append), but then returns a slice (which is a shallow copy of the list).
When you replace your original variable with the output of shift_to_left
, this behaviour is hidden:
In [1]: def shift_to_left(p):
...: p.append(p[0])
...: return p[1:]
...:
In [2]: xs = [1, 2, 3]
In [3]: xs = shift_to_left(xs)
In [4]: xs
Out[4]: [2, 3, 1]
But if we instead assign the result into a new variable, we can see that the original list has indeed been changed:
In [5]: ys = shift_to_left(xs)
In [6]: ys
Out[6]: [3, 1, 2]
In [7]: xs
Out[7]: [2, 3, 1, 2]
Our result, ys
, is the slice of xs
from the second element onwards. That's what you expected.
But xs
itself has also been changed by the call to append
: it's now one element longer than before.
This is what you're experiencing in your second example.
If you do not want this behaviour, one way of avoiding it is by passing a copy of your list to shift_to_left
:
In [8]: zs = shift_to_left(ys[:])
In [9]: zs
Out[9]: [1, 2, 3]
In [10]: ys
Out[10]: [3, 1, 2]
Here, you can see that the original list ys
has not been modified, as shift_to_left
was given a copy of it, not the object itself. (This is still passing by reference, of course; it's just not a reference to ys
).
Alternatively, and probably more reasonably, you could change shift_to_left
itself, so that it does not modify its arguments:
def shift_to_left(xs):
return xs[1:] + xs[0] # build a new list in the return statement
The big problem with both these approaches is that they create lots of copies of lists, which can be incredibly slow (and use a lot of memory) when the lists are large.
Of course, as @Marcin points out, if this is more than an academic exercise, you should probably use one of the built-in data structures such as deque
.