Reputation: 83
I know how thew syntax works for this, but I would like to know the process of how python replaces and alters the list on the left hand side. Ex.
L = [0, 1, 2, 3, 4]
L[0:2] = [5]
print L #L is now [5, 2, 3, 4]
How does python go about this?
Upvotes: 4
Views: 517
Reputation: 363233
In python 2.7, which your question is tagged with, it's the __setslice__
magic method. The details are documented here in the datamodel section.
__setslice__(self, i, j, sequence)
Called to implement assignment to self[i:j].
This is an old interface for backwards compatibility. If you, for example, assign to a slice with L[0:2:]
(which should be equivalent), you actually go through the new interface of __setitem__
.
To see what goes on under the hood, you can create your own class and inherit a list:
>>> class MyList(list):
... def __setslice__(self, *args):
... print '__setslice__ called with args:', args
... list.__setslice__(self, *args)
... def __setitem__(self, *args):
... print '__setitem__ called with args:', args
... list.__setitem__(self, *args)
...
Let's look at a few examples:
>>> L = MyList([0, 1, 2, 3, 4])
>>> L[0:2] = [5]
__setslice__ called with args: (0, 2, [5])
The 3 arguments passed are always the left and right endpoints, and then the object on the right hand side of the assignment.
>>> L[0:2:] = [5]
__setitem__ called with args: (slice(0, 2, None), [5])
Using the slice with a step will always go through the new interface of __setitem__
, and pass an instance of a slice object rather than start/stop indices.
>>> L[1:] = [5]
__setslice__ called with args: (1, 9223372036854775807, [5])
Omitting the endpoint uses sys.maxint
(omitting the start uses 0).
>>> L[::] = 'potato'
__setitem__ called with args: (slice(None, None, None), 'potato')
The object on the right doesn't have to be a list.
The last thing that I want to mention is that you only have to muck around with __setslice__
if you are deriving from the built-in list type. Usually you don't want to do that, if you are writing new code the modern way would be to inherit an abstract base class, specifically collections.Sequence
is the obvious choice for list-like structures. That way you don't have to worry about all the warts and hacks that the built-in list has for backwards compatibility.
Upvotes: 3
Reputation: 310097
This is accomplished with the __setitem__
or __setslice__
methods. (__setslice__
is deprecated IIRC and removed in python3.x).
For a list, the expression:
L[start: stop] = some_iterable
Will take the items from some_iterable
and replace the elements at indices from start to stop (non-inclusive). So, in your demo code, you have:
L[0:2] = [5]
This takes the elements at index 0
and 1
and replaces them with 5
.
Note that the replacement list doesn't need to be the same length as the the sublist that it is replacing.
Perhaps a better way to think of it is as an in-place way to do the following:
L[a:b] = c
# equivalent to the following operation (done in place)
L[:a] + list(c) + L[b:]
If you're actually curious about how it happens, the source code is the best reference. PyList_SetSlice calls list_ass_slice which turns the iterable on the right hand side into a sequence (tuple
or list
IIRC) . It resizes the array to hold the proper amount of data, copies the stuff on the right of the slice into the proper location and then copies in the new data. There are a few different code paths (and orders of operations) depending on whether the list grows or shrinks, but that's the basic gist of it.
Upvotes: 7