SpegooterCode
SpegooterCode

Reputation: 83

How does python do list slices and modification on the LHS?

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

Answers (2)

wim
wim

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

mgilson
mgilson

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

Related Questions