Python: Why Still List Items Don't Disappear After Using Procedure?
I define this function: [1,2,3] â [2,3,1]
def shift_to_left(p):
p.append(p[0])
return p[1:]
When I check like this, the results are ok:
p1 = [1,2,3] print p1 p1 = shift_to_left(p1) print p1
The result:
[1, 2, 3]
[2, 3, 1]
However, when I enter a different list and concatenation as I go, the result is different:
ss = [] p1 = [1,2,3] ss.append(p1) p1 = shift_to_left(p1) ss.append(p1) print ss
The result
[[1, 2, 3, 1], [2, 3, 1]]
But I want:
[1,2,3]
[2,3,1]
Why is this happening?
Many thanks,
source to share
In Python, most arguments are taken by reference.
Your function shift_to_left
actually mutates its argument (with append), but then returns a slice (which is an incomplete copy of the list).
When replacing the original variable with output, shift_to_left
this behavior 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 to a new variable, we can see that the original list has indeed been modified:
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 a slice xs
from the second element onwards. This is what you expected.
But itself has xs
also been changed by the call append
: this element is now longer than before. This is what you are experiencing in your second example.
If you don't want this behavior, one way to avoid it is to pass 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 was ys
not changed as shift_to_left
a copy of it was provided, not the object itself. (Of course, this is by reference, it just isn't a link to ys
).
Alternatively, and perhaps smarter, you can change shift_to_left
yourself so that it doesn't change 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 of these approaches is that they create many copies of the lists, which can be incredibly slow (and use up 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 like deque
.
source to share
If you run your code here , you may notice that it ss
remains to point to the original (mutated in your shift function due to p.append(p[0])
) copy p1
, where as p1
points to a familiar list together when it gets reassigned, resulting in behavior. (Step 10 of 11)
( p
becomes mutated as well ss[0] = p
)
( p1
completely assigned to the new list, which is added last to ss
)
source to share
Why is this happening?
return p[1:]
is "non-destructive": it creates a new list. However, it p.append(p[0])
is "destructive": it changes itself p
.
First add p1
to ss
. It does [[1, 2, 3]]
where [1, 2, 3]
equal p1
.
Then you make your own shift_to_left
, which changes p1
to [1, 2, 3, 1]
and returns [2, 3, 1]
. Since it is p1
contained in ss
, your ss
becomes [[1, 2, 3, 1]]
, and then you add a new one p1
to the form [[1, 2, 3, 1], [2, 3, 1]]
.
A better implementation would be purely non-destructive:
def shift_to_left(p):
return p[1:] + [p[0]]
source to share