How does assignment work with list slices?
You are confusing two distinct operation that use very similar syntax:
1) slicing:
b = a[0:2]
This makes a copy of the slice of a
and assigns it to b
.
2) slice assignment:
a[0:2] = b
This replaces the slice of a
with the contents of b
.
Although the syntax is similar (I imagine by design!), these are two different operations.
What is the difference between slice assignment that slices the whole list and direct assignment?
a_list = ['foo', 'bar']
Creates a new list
in memory and points the name a_list
at it. It is irrelevant what a_list
pointed at before.
a_list[:] = ['foo', 'bar']
Calls the __setitem__
method of the a_list
object with a slice
as the index, and a new list
created in memory as the value.
__setitem__
evaluates the slice
to figure out what indexes it represents, and calls iter
on the value it was passed. It then iterates over the object, setting each index within the range specified by the slice
to the next value from the object. For list
s, if the range specified by the slice
is not the same length as the iterable, the list
is resized. This allows you to do a number of interesting things, like delete sections of a list:
a_list[:] = [] # deletes all the items in the list, equivalent to 'del a_list[:]'
or inserting new values in the middle of a list:
a_list[1:1] = [1, 2, 3] # inserts the new values at index 1 in the list
However, with "extended slices", where the step
is not one, the iterable must be the correct length:
>>> lst = [1, 2, 3]
>>> lst[::2] = []
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
ValueError: attempt to assign sequence of size 0 to extended slice of size 2
The main things that are different about slice assignment to a_list
are:
a_list
must already point to an object- That object is modified, instead of pointing
a_list
at a new object - That object must support
__setitem__
with aslice
index - The object on the right must support iteration
- No name is pointed at the object on the right. If there are no other references to it (such as when it is a literal as in your example), it will be reference counted out of existence after the iteration is complete.
How is this list expanded with the slicing assignment?
Slice assignment replaces the specified part of the list with the iterable on the right-hand side, which may have a different length than the slice. Taking the question at face value, the reason why this is so is because it's convenient.
You are not really assigning to the slice, i.e. Python doesn't produce a slice object that contains the specified values from the list and then changes these values. One reason that wouldn't work is that slicing returns a new list, so this operation wouldn't change the original list.
Also see this question, which emphasizes that slicing and slice assignment are totally different.
Explanation of iterable object assignment in specific range by list slicing (ex, arr[1:3] = 'ABCD')
Slice assignment takes an iterable on the right-hand. Many things can be iterables, an array or list, for example [8, 9]
:
arr = [1, 2, 3, 4, 5]
arr[1:3] = [8, 9]
arr
[1, 8, 9, 4, 5]
A string is an iterable of characters, as you can see in this example
for x in 'ABCD':
print(x)
A
B
C
D
which is why get the result you got. So if what you want is to replace the arr[1:3]
slice with a single array element that's a string, you need to give it an iterable that yields that element:
arr = [1, 2, 3, 4, 5]
arr[1:3] = ['ABCD']
arr
[1, 'ABCD', 4, 5]
Note the brackets around the string in the slice assignment statement.
Slice Assignment with a String in a List
Two important points:
- Slice assignment takes an iterable on the right-hand side, and replaces the elements of the slice with the objects produced by the iterable.
- In Python, strings are iterable: iterating over a string yields its characters.
Thus
L1[0:1] = 'cake'
replaces the first element of L1
with the individual characters of 'cake'
.
To replace the first element with the string 'cake'
, simply write:
L1[0] = 'cake'
or, using the slice assignment syntax:
L1[0:1] = ['cake']
Slice assignment with empty list - why?
I think the confusion comes from the fact that you are indeed slicing and not indexing. Observe:
>>> L = ['a','b','c','d']
>>> L[2] = []
>>> L
['a', 'b', [], 'd']
Note that L[2:3]
is "identical" to L[2]
in regards to the range of 2-3 just being 2 (because slicing is non-inclusive). However, slicing and indexing are two different behaviours, because slicing can change the length of the list, whereas indexing simply modifies an existing element.
The reason why the empty list works that way is because of how the slice assignment works. When you're assigning to a slice, python unpacks what you give it and stores its results into the indexes. Observe:
>>> L = ['a', 'b', 'c', 'd']
>>> L[2:4] = 'e'
>>> L
['a', 'b', 'e']
>>> L = ['a', 'b', 'c', 'd']
>>> L[2:4] = 'efgh'
>>> L
['a', 'b', 'e', 'f', 'g', 'h']
The behaviour of L[2:4] = 'e'
and L[2:4] = ['e']
is the same in python because of the behaviour of strings in Python: they may be iterated through and hence unpacked.
So when you put L[2:4] = []
, it will unpack all the elements of []
and assign their contents into indexes 2 and 3. Of course, since there are no elements, it will assign them to nothing (note: different to None
or ""
- literally nothing), and hence be deleted.
List slice assignment with resize using negative indices
This is expected behavior for extended slices. As you have discovered, it requires the same number of elements to be assigned to the slice. Here's documentation that refers to the issue.
It becomes more obvious why if instead you sliced p[1:5:2]
. It's not a nice contiguous block that you can easily resize.
p[2:6:1]
works because it's essentially a regular slice, ie p[2:6]
.
Related Topics
Best Way to Convert String to Bytes in Python 3
Why Should Exec() and Eval() Be Avoided
Converting a List to a Set Changes Element Order
Find the Current Directory and File'S Directory
Find the Row Indexes of Several Values in a Numpy Array
How to Find Overlapping Matches With a Regexp
Numpy or Pandas: Keeping Array Type as Integer While Having a Nan Value
Groupby Value Counts on the Dataframe Pandas
Remove Specific Characters from a String in Python
How to Do Fuzzy Match Merge With Python Pandas
Prevent Scientific Notation in Matplotlib.Pyplot
Selecting Multiple Columns in a Pandas Dataframe
Multiple Assignment and Evaluation Order in Python
How to Play Wav File in Python