How does list assignment work using range?

Asked

Viewed 2,699 times

15

I was reading a documentation on the internet when I came across the following code:

sys.path[:0] = new_sys_path

I was curious, because I had never seen intervals used on the left side of an assignment (for me they created a sublist, and only). A quick test showed me that this code was prefixing one list to another:

>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> x[:0] = y
>>> x
[4, 5, 6, 1, 2, 3]

But I still don’t understand the logic of the indexes: why use [:0] placed the other list at the beginning? What values can be used in this range, and what do they mean? There are other interesting ways to use this feature, besides prefixing?

  • I don’t know in Python but I’ve seen some things like this in other languages, and you can do some interesting things, including with strings changeable. Nothing you can’t do otherwise, but simplify.

  • 1

    Really different. If anyone risks trying to understand and adapt, it seems that there is an explanation in English here: http://stackoverflow.com/a/30221031/1796236

  • 2

    The logic is to take all values prior to the first item in the list x[:0] and replace by y, as well as x[:1] will replace the interval (-infinity up to 1) by the list, the operation is the same as x[0] = 1 or x[1:] = [2, 3].

  • This one I didn’t know either. It reminded me of the Mid$ in VB6 :)

1 answer

11


The interval on the left side of the assignment indicates that that interval (before the change) will be replaced by whatever comes after the assignment (which must be a sequence or an iterable)

Thus, an assignment to x[:0] = ... will insert the values of the expression on the right in the interval "between the beginning of the list, and the zero position" - ie, will put new values before the initial.

An attribution to the slice [:] will swap all content from the "start" list (empty before :) to "the end" (empty after :) by the content generated in the left expression iteration.

And there is even a "language" that is often used to empty an existing list inplace - that is, the list object, which may have references elsewhere remains the same, but all its elements are removed: just associate an endless void to the entire contents of the list:

x[:] = []

Example:

>>> 
>>> x = [1, 2, 3]
>>> y = x
>>> id(x)
140612754938440
>>> x[:] = []
>>> y
[]
>>> id(x)
140612754938440

So, x[2] = [1,2,3] will simply put a new list 1,2,3 at position 2 of the original list. But, x[2:2] = [1,2,3]is saying: "replace all content starting at position 2 and going right before position 2 (i.e., an "empty mathematical point before the third element of the list") with the elements of the sequence [1,2,3]: this will insert these three elements in the position immediately preceding the third element. The "1" becomes the new third element, and all the rest of the list is pushed 3 forward positions:

>>> a = list(range(10,20))
>>> a
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> a[2:2] = [1,2,3]
>>> a
[10, 11, 1, 2, 3, 12, 13, 14, 15, 16, 17, 18, 19]

It’s different from the method insert of lists he pushes all the rest forward, but puts his argument as a single element in the indicated position:

>>> a = list(range(10,20))
>>> a
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> a.insert(2, [1,2,3])
>>> a
[10, 11, [1, 2, 3], 12, 13, 14, 15, 16, 17, 18, 19]
>>> 

There is also another form of use of the "assignment to Slices" that uses the third parameter of Slices, the step (step). In this case however, the list does not increase or decrease in size according to the size of the eternal: the number of elements of the assignment has to be exactly equal to the number of elements that will be replaced.

For example, "replace every third element of my list from start to finish with the string 'Fizz'":

a = list(range(10))
a[::3] = ["fizz"] * 4

Under the hood

And is that magic? No - any Python object can deliver results when it is triggered by the [] - he just needs his class to implement the method __getitem__ . To accept assignments using the x[ ] = ...the object must be of a class that implements __setitem__. If you look at the official documentation of the magical methods, in https://docs.python.org/3/reference/datamodel.html#emulating-container-types - will realize that these methods are standard Python methods - the __getitem__ has a signature like:

def __getitem__(self, index):
     ....

What happens when someone uses the notation of : between brackets is that Python creates an object slice and sends it in place of index - Slice has the attributes start, stop and step (an omission of number before or after a : puts a None in the respective attribute).

So if you want to emulate a container type that uses slice notation for its own purposes, just detect objects of the type slice come in these parameters (with isinstance) and respond accordingly.

More use of indexes between brackets

One form that is not used in the standard library, but which has extensive use in 3rd party modules (in particular Numpy - the de facto library for scientific computing in Python) is to separate indexes with a comma within the brackets. This makes the index parameter a Python tuple, and can normally be used to index multimensional objects (Numpy uses for matrices). Furthermore, you can use the notation of slices within the expressions separated by a circle, within the brackets.

For example, we will use Numpy to create a matrix of 5 x 5 integers, fill it with "1" and use the syntax of Slices to "clean" a 3x3 window in the center of the matrix, with zeros. Note that unlike Python lists, Numpy accepts a non-extendable element on the left side of an assignment with Slices: it places that element in all of the described positions.

>>> import numpy as np
>>> a = np.zeros((10,), dtype=np.uint32)
>>> a
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=uint32)
>>> a[:] = 5
>>> a
array([5, 5, 5, 5, 5, 5, 5, 5, 5, 5], dtype=uint32)

And for the example of the "window":

>>> a = np.zeros((5,5), dtype=np.uint32)
>>> a[:, :] = 1
>>> a
array([[1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1]], dtype=uint32)
>>> a[1:4, 1:4] = 0
>>> a
array([[1, 1, 1, 1, 1],
       [1, 0, 0, 0, 1],
       [1, 0, 0, 0, 1],
       [1, 0, 0, 0, 1],
       [1, 1, 1, 1, 1]], dtype=uint32)

I also want!

If you want to try playing with indexes, just implement your own class with __getitem__, __setitem__ and __delitem__ - you can both create a subclass of list how to create your iterator from scratch. Create a subclass of list directly has some contraindications: Python has some internal optimizations that can cause not all special methods (those that have __nomeassim__ ) are called for all operations as expected. For this there is the class collections.UserList (from UserList import UserList python2) in the standard library. It behaves exactly like a list, but is meant to be inherited. To create from scratch, inherit a class of collections.abc.MutableSequence and implement the respective methods described in https://docs.python.org/3/library/collections.abc.html - (collections.MutableSequence in Python 2.x - without the abc).

So to have a list that when receiving a slice simply insert the value of the expression on the right at all positions of the slice, without trying to iterate the object, just do:

from collections import UserList

class MyList(UserList):
    def __setitem__(self, index, value):
        if isinstance(index, slice):
            start = index.start if index.start is not None else 0
            stop = index.stop if index.stop is not None else len(self)
            step = index.step if index.step is not None else 1
            for i in range(start, stop, step):
                 self[i] = value
            return
        super().__setitem__(index, value)

And so:

>>> a = MyList(range(10))
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[::3] = "fizz"
0 10 3
>>> a
['fizz', 1, 2, 'fizz', 4, 5, 'fizz', 7, 8, 'fizz']
>>> 

(For a normal list, fizz would be treated as an eternal, and each letter would be used in one place - the result would be: ['f', 1, 2, 'i', 4, 5, 'z', 7, 8, 'z'])

Browser other questions tagged

You are not signed in. Login or sign up in order to post.