Consider this:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
These are correct statements in Python to remove elements:
numbers[0:2] = []
numbers[3:5] = []
However the statement below is not allowed:
numbers[::2] = []
ValueError: attempt to assign sequence of size 0 to extended slice of size 5
What prevents such a statement in Python?
ValueError: attempt to assign sequence of size 0 to extended slice of size 5
What prevents such a statement in Python?
It is noted in the documentation that the replacement must have the same length for the case where there is an explicit step (which is 2 in your case).
Operation
Result
Notes
s[i:j] = t
slice of s from i to j is replaced by the contents of the iterable t
s[i:j:k] = t
the elements of s[i:j:k] are replaced by those of t
(1) t must have the same length as the slice it is replacing.
The correct way is also documented there.
Operation
Result
Notes
del s[i:j]
same as s[i:j] = []
del s[i:j:k]
removes the elements of s[i:j:k] from the list
Code:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
del numbers[::2]
print(numbers)
Output:
[1, 3, 5, 7, 9]
There is no need for numbers[::2] = [] to have the behaviour of deleting every second element, because you can already do that by writing numbers[:] = numbers[1::2]. If instead you want to (e.g.) replace every second element with the value 1, you can write one of the below, which are explicit about their behaviour.
for i in range(0, len(numbers), 2):
numbers[i] = 1
# or:
numbers[::2] = [1] * len(numbers[::2])
It is not obvious what the correct behaviour should be for assigning m elements to a non-contiguous slice of n list locations when m != n. In the comments you propose a possible behaviour, but your proposal is not consistent with how slice assignment works in other cases (normally, each element on the right-hand side gets used once on the left-hand side) and certainly doesn't fulfil the principle of least astonishment. In cases like this, I think there is no (non-raising) behaviour that most people would expect, so raising an error is the best option.
You have to create a new list with the slicing already mentioned in other answers:
numbers = numbers[1::2]
if you work on the same list you incur into heavy performance loss because inserting or deleting (not appending!) an element to a list is O(N). Since you have O(N) for each insertion and you have N/2 insertions your total cost is O(N**2). You really don't want that cost.
Creating a new list with the output of the slicing, on the other hand, has just O(N) total cost.
Given the below examples:
array = [1,2,3,4,0]
In: array[0] += 2
Out: 3
In: array[1:3] += 2
Out: TypeError: 'int' object is not iterable
In: array[1:3] += [100, 100]
Out: [1, 2, 3, 100, 100, 4, 5]
Can someone explain me why the two last examples wont return something like [1,102,103,4,0] AND if it is possible doing this with a simple slice, not using a for loop...
When using the slice operator it refers to sub-part of the list, so operating on it requires a list too (we can use the "add" operator on list with list, and not with an int, unlike in some other languages).
Therefore the following:
array[1:3] += 2
Throws:
TypeError: 'int' object is not iterable
Because 2 is not a list (actually an iterable, which is more general than list).
But:
array[1:3] += [100, 100]
Works and adds (actually appends) the two elements in the middle (index 3) of array according to indexes:
[3, 2, 3, 100, 100, 4, 0]
Without using a for loop, as requested
If you want to add to the values in the slice:
array = [1,2,3,4,0]
array.__setitem__(slice(1, 3), [x+2 for x in array[1:3]])
# [1, 102, 103, 4, 0]
print(array)
Which can be written also as:
array = [1,2,3,4,0]
def apply_on_slice(lst, start, end, callable):
array.__setitem__(slice(start, end), [callable(x) for x in array[start:end]])
apply_on_slice(array, 1, 3, lambda x : x + 100)
# [1, 102, 103, 4, 0]
print(array)
Using a for loop
Here are some other options to do so, elegantly:
array[1:3] = (x+2 for x in array[1:3])
Or of course, using a regular for loop, which is more efficient than using slicing twice:
for i in range(1, 3):
array[i] += 2
You are clearly expecting the operation to be applied element-wise, as in R and other languages (and within Python, in numpy arrays and the like). E.g., adding 2 to a list would add 2 to each of the list's elements. This is not how Python lists work: Each of the statements you ask about constructs one object on each side of the operator (a list or list slice, a list element, an integer), then applies the operation (just once) to these two objects. So if you "add" two lists you get concatenation, if you try to add a list and an int you get a TypeError, etc. The details you can read in #Aviv's answer.
This question already has answers here:
How does assignment work with list slices?
(5 answers)
Closed 5 years ago.
I came across the following code (sort of):
my_list = [1, [2, 3, 4], 5]
my_list[1:2] = my_list[1]
After running these two lines, the variable my_list will be [1, 2, 3, 4, 5]. Pretty useful for expanding nested lists.
But why does it actually do what it does?
I would have assumed that the statement my_list[1:2] = my_list[1] would do one of the following:
simply put [2, 3, 4] into the second position in the list (where it already is)
give some kind of "too many values to unpack" error, from trying to put three values (namely 2,3,4) into a container of only length 1 (namely my_list[1:2]). (Repeating the above with a Numpy array instead of a list results in a similar error.)
Other questions (e.g. How assignment works with python list slice) tend to not pay much attention to the discrepancy between the size of the slice to be replaced, and the size of the items you're replacing it with. (Let alone explaining why it works the way it does.)
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.
Here is the relevant bit from the Python Language Reference
If the target is a slicing: The primary expression in the reference is
evaluated. It should yield a mutable sequence object (such as a list).
The assigned object should be a sequence object of the same type.
Next, the lower and upper bound expressions are evaluated, insofar
they are present; defaults are zero and the sequence’s length. The
bounds should evaluate to integers. If either bound is negative, the
sequence’s length is added to it. The resulting bounds are clipped to
lie between zero and the sequence’s length, inclusive. Finally, the
sequence object is asked to replace the slice with the items of the
assigned sequence. The length of the slice may be different from the
length of the assigned sequence, thus changing the length of the
target sequence, if the target sequence allows it.
This behavior makes sense qualitatively because when you slice a list you get a sub list so replacing that with another list shouldn't add a level of nesting. Allowing it to change the length of the list is a design choice. Other choices are possible as your numpy example demonstrates.
Short Answer:
my_list[1:2] = my_list[1] will replaced the content from 1st index to 2nd index of my_list with the content of present in 1st index of
my_list
Explanation:
Let's see two slicing operations, very similar but totally distinct
This creates the copy of list and stores it the variable
some_variable = some_list[2:5]
This replaces the content of the list inplace, which permits changing the length of the list too.
some_list[2:5] = [1, 2, 3, 4]
When you use assignment operator =, it invokes a __setitem__ function. Our focus here is the case 2 above. As per the Python's Assignment Statement document:
If the target is a slicing: The primary expression in the reference is
evaluated. It should yield a mutable sequence object (such as a list).
The assigned object should be a sequence object of the same type.
Next, the lower and upper bound expressions are evaluated, insofar
they are present; defaults are zero and the sequence’s length. The
bounds should evaluate to integers. If either bound is negative, the
sequence’s length is added to it. The resulting bounds are clipped to
lie between zero and the sequence’s length, inclusive. Finally, the
sequence object is asked to replace the slice with the items of the
assigned sequence. The length of the slice may be different from the
length of the assigned sequence, thus changing the length of the
target sequence, if the target sequence allows it.
In our case my_list[1:2] = my_list[1], python will also call __setitem__ as:
my_list.__setitem__(slice(1,2,None), [2, 3, 4])
Refer slice document to know what it does.
So, when you did my_list[1:2] = my_list[1], you replaced the content from 1st index to 2nd index of my_list with the content of present in 1st index of my_list i.e. [2, 3, 4].
I think now we can answer why your assumptions are incorrect:
put [2, 3, 4] into the second position in the list (where it already is)
No. Because __setitem__ is not called on the index but on the slice of the indices which you passed.
give some kind of "too many values to unpack" error, from trying to put three values (namely 2,3,4) into a container of only length 1 (namely my_list[1:2]).
Again No. Because the range of your indices creating your container is replaced with new set of values.
What you are doing is slice assignment.
Assignment to slices is also possible, and this can even change the size of the list or clear it entirely
my_list[1:2] = my_list[1]
This replaces the slice of my_list with the contents of my_list[1].
By specifying my_list[1:2] on the left side of the assignment operator =, you are telling Python you want to use slice assignment.
my_list[1:2] = my_list[1] is equivalent to my_list.__setitem__(slice(1, 2, None), my_list[1])
In slice(1, 2, None), 1 is start, 2 is stop, and None is step and is optional.
What you are trying here is called Slice Assingnment. In python it is possible to assign an iterable(my_list[1] in your case) to a slice of another iterable(my_list[0:1] in your case). Lets walk through some examples to understand what it really means:
>>> l = [1,2,3,4,5]
>>> b = [6,7,8]
>>> l[0:3] = b
>>> l
>>> [6, 7, 8, 4, 5]
So what happened here is the portion of list l for 0,1,2 indices is
which covers elements 1,2,3is replaced by elements of list b 6,7,8. However in this case size of slice and replaced elements happens to be equal by chance.
So what happens when slice size and iterable to be replaced are not equal
>>> l = [1,2,3,4,5]
>>> b = [6,7,8]
>>> l[0:4] = b
>>> l
>>> [6,7,8,5]
Notice that this operation didn't produce any error, instead, it just copied whatever elements are available with the entire sliced portion. In this case, sliced elements are 1,2,3,4 replaced by 6,7,8
In the previous example iterable to be replaced was smaller. What happens if slice portion is smaller
>>> l = [1,2,3,4,5]
>>> b = [6,7,8]
>>> l[0:1] = b
>>> l
>>> [6,7,8,2,3,4,5]
So now we can see that only first element is replaced by entire iterable b.
You can also use this behaviour to remove a specific portion of the list ( Which I find convenient in some situations ).
>>> l = [1,2,3,4,5]
>>> l[0:2] = []
>>> l
>>> [3,4,5]
First two elements are removed very conveniently here.
So the example in your question is similar to the examples I posted above, except that in your case there is an additional step of unpacking list values. Unpacking list value happens every time when you assign list to another list. A short example
>>> l = [[1]]
>>> a = []
>>> a = l[0]
>>> a
>>> [1]
Your example now:
#Replace the slice [0:1] with my_list[1] perform unpacking list values as well
>>> my_list[1:2] = my_list[1]
>>> [1,2,3,4,5]
Also note that slice assignment is only possible if you assign iterable to a slice. If you try to assign an int or something that is not an iterable to a slice python will throw an error.
>>> l = [1,2,3,4,5]
>>> b = [6,7,8]
>>> l[0:1] = b[1]
>>> Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable
That's why in your case my_list[1] don't raise an error since it is an iterable.
That what you are doing is insert an element through slicing. I will explain everything by parts. More than an inserting it could be interpreted as adding an item to your list after slicing target list in a range desired. Now to explain every line in detail:
my_list[1:2]
That part is like saying to Python; "I want to get the values (slice) from index 1 to the index before 2 (excluding 2, I will explain with another example later on)". After that you assign a value to those values:
my_list[1:2] = my_list[1] #The same as my_list[1:2] = [2,3,4]
Now that you know what the first part does, next it is going to add the item at the right side of the '=' operator so you could interpret it like this; "I want to slice from index 1 to everything before 2 (again, excluding index 2) and then add my list [2,3,4]". Now here comes another examples so you understand even better I hope.
problemList = [1, [2, 3, 4], 5]
problemList[1:2] = problemList[1] #The same as problemList[1:2] = [2,3,4]
analogProblemL = [1] + [2,3,4] + [5] #Output : [1,2,3,4,5]
insertList = [12,13,14]
myList = [1, [2, 3, 4], 5,6,7,8,9]
myList[3:6] = insertList
analogFstList = [1,[2,3,4] ,5] + insertList + [9] #Output : [1,[2,3,4],5,12,13,14,9]
myScnList = [1, [2, 3, 4], 5]
myScnList[1:3] = [2,3,4]
analogScnList = [1] + [2,3,4] + [5] #Output : [1,2,3,4,5]
The next lines will be like if it was an animation frames so it's easier to interpret:
[1,2,3,4,5] #List before line of code: myList[1:3] = [12,13,14]
[1,|2,3|,4,5] #The moment where you slice the list with: myList[1:3]. Please notice that the characters used '|' are for representing the slice.
[1] + [12,13,14] + [4,5] #After assigning what the slice should be changed for. It's like excracting from the whole list the values [2,3] and changing it for [12,13,14].
[1,12,13,14,4,5] #Final list after running the code.
Some references used for this answer:
http://effbot.org/zone/python-list.htm
Understanding Python's slice notation
How assignment works with python list slice
Hope it was useful for you.
I am trying to define a function that returns a list without the first and last items in that list. However, I get this when I run the function: "TypeError: 'builtin_function_or_method' object does not support item deletion".
This is my code so far:
def middle(t):
"""returns a copy of a list with the first and last items removed
list -> list"""
t = input
del t[0]
del t[-1]
print(t)
Any help is appreciated.
t=input is assigning t to the function object input. You can't slice a function. t[1:-1] will return a new list with the first and last items removed.
You should delete the t = input line; that's assigning t to the built-in function input, which isn't an array and isn't what you want. Once you do that, you can use:
l = [0, 1, 2, 3, 4]
middle(l)
which will leave l = [1, 2, 3].
However, a better way of doing this is just to say
l = [0, 1, 2, 3, 4]
l2 = l[1:-1]
This leaves l2 as [1, 2, 3], as I assume you wanted.
If I were you, I'd go with something like
themiddlevalues = t[1:-1]
This works on any sort of sequence, and doesn't require a function. It's probably worth learning about python slice notation, as slices are important to numpy, etc. See http://codingbat.com/doc/python-strings.html, as slices work the same way across strings, lists, etc.
So I can create a reverse iterator on a list:
list(reversed([0,1,2,3]))
[3, 2, 1, 0]
I assume this simply calls getitem from index len(...)-1 to 0. But then I cannot also do this:
list(reversed(xrange(4)))
[3, 2, 1, 0]
Now I am a bit confused. Does this create the list from xrange(4) and then reverse it? If not, how does it know what the last element is and how to go backwards? I read the documentation but it didn't help.
reversed() looks for a __reversed__ special method on the object. List objects provide this, and so does xrange():
>>> xrange(4).__reversed__()
<rangeiterator object at 0x106e2fa50>
The iterator object simply produces the values in reverse, no list object is produced.
For objects that do not implement __reversed__, the reversed() function uses the length and the __getitem__ method; e.g. reversed() is essentially equivalent to:
def reversed(seq):
try:
return seq.__reversed__()
except AttributeError:
return (seq[i] for i in xrange(len(seq) - 1, -1 , -1))
where the second part is a generator expression, evaluating lazily. The generator then accesses each item in turn starting at index (length - 1) all the way down to index 0.
reversed() can only take a sequence -- if it took generic iterators, it couldn't know what the final value was without exhausting the iterator and storing all the values.
Luckily, xrange returns an xrange object that works as a sequence:
>>> x = xrange(10)
>>> len(x)
10
>>> x[9]
9
It also happens to have an actual __reversed__ method, but that's a special case of having all the sequence methods.
Just compare the two:
In [2]: reversed(xrange(4))
Out[2]: <rangeiterator at 0x7fa83291bde0>
In [3]: list(reversed(xrange(4)))
Out[3]: [3, 2, 1, 0]
In [4]: reversed([0,1,2,3])
Out[4]: <listreverseiterator at 0x7fa8328be2d0>
In [5]: list(reversed([0,1,2,3]))
Out[5]: [3, 2, 1, 0]