Why was p[:] designed to work differently in these two situations?
As others have stated; p[:]
deletes all items in p
; BUT will not affect q. To go into further detail the list docs refer to just this:
All slice operations return a new list containing the requested elements. This means that the following slice returns a new (shallow) copy of the list:
>>> squares = [1, 4, 9, 16, 25] ... >>> squares[:] [1, 4, 9, 16, 25]
So q=p[:]
creates a (shallow) copy of p
as a separate list but upon further inspection it does point to a completely separate location in memory.
>>> p = [1,2,3]
>>> q=p[:]
>>> id(q)
139646232329032
>>> id(p)
139646232627080
This is explained better in the copy
module:
A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
Although the del statement is performed recursively on lists/slices:
Deletion of a target list recursively deletes each target, from left to right.
So if we use del p[:]
we are deleting the contents of p
by iterating over each element, whereas q
is not altered as stated earlier, it references a separate list although having the same items:
>>> del p[:]
>>> p
[]
>>> q
[1, 2, 3]
In fact this is also referenced in the list docs as well in the list.clear
method:
list.copy()
Return a shallow copy of the list. Equivalent to
a[:]
.list.clear()
Remove all items from the list. Equivalent to
del a[:]
.
del and assignments are designed consistently, they're just not designed the way you expected them to be. del never deletes objects, it deletes names/references (object deletion only ever happens indirectly, it's the refcount/garbage collector that deletes the objects); similarly the assignment operator never copies objects, it's always creating/updating names/references.
The del and assignment operator takes a reference specification (similar to the concept of an lvalue in C, though the details differs). This reference specification is either a variable name (plain identifier), a __setitem__
key (object in square bracket), or __setattr__
name (identifier after dot). This lvalue is not evaluated like an expression, as doing that will make it impossible to assign or delete anything.
Consider the symmetry between:
p[:] = [1, 2, 3]
and
del p[:]
In both cases, p[:]
works identically because they are both evaluated as an lvalue. On the other hand, in the following code, p[:]
is an expression that is fully evaluated into an object:
q = p[:]
del
on iterator is just a call to __delitem__
with index as argument. Just like parenthesis call [n] is a call to __getitem__
method on iterator instance with index n.
So when you call p[:]
you are creating a sequence of items, and when you call del p[:]
you map that del/__delitem__ to every item in that sequence.