p = [1,2,3]
print(p) # [1, 2, 3]
q=p[:] # supposed to do a shallow copy
q[0]=11
print(q) #[11, 2, 3]
print(p) #[1, 2, 3]
# above confirms that q is not p, and is a d
Historical reasons, mainly.
In early versions of Python, iterators and generators weren't really a thing. Most ways of working with sequences just returned lists: range()
, for example, returned a fully-constructed list containing the numbers.
So it made sense for slices, when used on the right-hand side of an expression, to return a list. a[i:j:s]
returned a new list containing selected elements from a
. And so a[:]
on the right-hand side of an assignment would return a new list containing all the elements of a
, that is, a shallow copy: this was perfectly consistent at the time.
On the other hand, brackets on the left side of an expression always modified the original list: that was the precedent set by a[i] = d
, and that precedent was followed by del a[i]
, and then by del a[i:j]
.
Time passed, and copying values and instantiating new lists all over the place was seen as unnecessary and expensive. Nowadays, range()
returns a generator that produces each number only as it's requested, and iterating over a slice could potentially work the same way—but the idiom of copy = original[:]
is too well-entrenched as a historical artifact.
In Numpy, by the way, this isn't the case: ref = original[:]
will make a reference rather than a shallow copy, which is consistent with how del
and assignment to arrays work.
>>> a = np.array([1,2,3,4])
>>> b = a[:]
>>> a[1] = 7
>>> b
array([1, 7, 3, 4])
Python 4, if it ever happens, may follow suit. It is, as you've observed, much more consistent with other behavior.