DEV Community

darkmage
darkmage

Posted on

Python Slices As L-Values

Earlier today, a student and I were exploring academic Python challenges on a website called Finxter, and came across an interesting problem:

a = [1, 2, 3]
a[2:4] = [4, 5, 6]
print(a)
Enter fullscreen mode Exit fullscreen mode

I, unexpectedly, observed that this code results in [1, 2, 4, 5, 6] being printed out, but it would not have been unexpected had I been properly educated in Python myself.

As a Computer Science instructor and tutor, I have assisted in figuring out uncertainties in student code across a variety of languages and situations. Often, one encounters code that they might not have seen before, or code written in a way or with a context that is unexpected.

I've been writing Python for over 6 years now, and had not once ever considered putting a list-slice to the left of the equals sign!

So, considering the unexpectedness of this code, what would I have intuited that it should have yielded instead?

Well, my first thought was that the length of the slice was longer than the length of the list, so there should have been an error of some sort indicating to me that I was dabbling in dangerous territory

Coming from a strong background in C (I only call it "strong" in that it is a language I have spent enough time with that it impacts how I think about the construction of programs -- an approach that has it's own pros and cons, more on that in a future post), my thinking was that by stepping outside of the bounds of the list in the slice, that I was violating something and perhaps an Exception should be thrown. In a language like C, there are no protections, so attempting to access a memory location in this way may or may not actually crash the program, but certainly exposes it to doing so.

But no, this is not what happens.

Python happy doesn't care what comes after the : in a list slice as an l-value.

a = [1, 2, 3]

a[2:4] = [4, 5, 6] # this is equal to
a[2:]  = [4, 5, 6] # this

print(a)           # [1, 2, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

To observe the difference between list-slice as l-value and simply assigning a value to a location:

a = [1, 2, 3]

a[2:] = [4, 5, 6] # list-slice as l-value
print(a)          # [1, 2, 4, 5, 6]

a = [1, 2, 3]

a[2] = [4, 5, 6] # regular assignment
print(a)         # [1, 2, [4, 5, 6]]
Enter fullscreen mode Exit fullscreen mode

Ok, so, big deal, why does this matter?

I asked myself "I wonder what happens if I use this technique to assign values past the last index in the list?"

a = [1, 2, 3]
a[9001:] = [4, 5, 6]

print(a) # [1, 2, 3, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

This is effectively equivalent to appending items to the list, but does so in one statement.

a = [1, 2, 3]
a.append(4)
a.append(5)
a.append(6)

a = [1, 2, 3]
a[7777:] = [4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

I just wanted to share the discovery of a quirk in the Python language that is new not only to me, but also my student as well :)

Happy Thanksgiving everyone!


Oldest comments (0)