Simplify slicing

This is the original code.

a = b [0:1] + b [3:4]

This is the simplified code.

a = b [0:1 3:4]

# or
a = b [0:1,3:4]

That doesn’t look any simpler to me. The first version looks like a mistake (maybe they intended [0:13:4]?), and the second example conflicts with other uses of indexing, like numpy arrays.

5 Likes

More importantly, this is a breaking change :frowning:, because slices can hold any arbitrary object.

  • Parsing whitespace wouldn’t work for strings, because a[0:"a" "b":2] concatenates the string into "ab", which is indeed valid right now.
  • 1,3 currently evaluates to a tuple, which is also valid.

Not in this context because the colon takes priority over the comma, 1:2,3:4 as a key already means (slice(1, 2, None), slice(3, 4, None)).

>>> class Container:
...     def __getitem__(self, key):
...         return key
... 
>>> Container()[1:2,3:4]
(slice(1, 2, None), slice(3, 4, None))
2 Likes

“”“python
a = b[0:1] [3:4]
“””
How does this code look?

This means: Slice from index 3 to 4 from this one-element list.

I wouldn’t characterize the second example as a “conflict” with numpy arrays. The proposal is about list slicing after all. Different classes are free to interpret indexing in different ways. And I think b[0:1, 3:4] where b is a list feels intuitive enough to be taught as a concatenation of multiple ranges/slices.

Questions related to slicing in multiple ranges are rather frequently asked on StackOverflow, and the existing solutions are either inefficient (with +), not super readable (with itertools.chain), clunky (with for loops) or requiring a third-party library, so there may be value in adding built-in support for slicing in multiple ranges for lists.

The proposal just says “slicing” and there’s no indication that b is a list. It’s just a thing getting sliced.

1 Like

Ah you’re right that the OP didn’t explicitly say it’s for a list. I just presumed it’s for a list because the OP uses the + operator. I stand by the rest of what I said though.

You can also implement a slicer yourself like this:

class Slicer:
    def __init__(self, seq):
        self.seq = seq

    def __getitem__(self, index):
        if isinstance(index, tuple):
            return type(self.seq)(
                self.seq[i]
                for s in index
                for i in range(*s.indices(len(self.seq)))
            )
        return self.seq[index]

so that:

b = list(range(10))
print(Slicer(b)[1:4, 7:9])

outputs:

[1, 2, 3, 7, 8]
1 Like

Why not just use the slice on the sequence?

            return type(self.seq)(
                x
                for s in index
                for x in self.seq[s]
            )

Attempt This Online!

Because most sequence types create new copies when sliced and I was trying to avoid creating intermediate sequences.

List slicing is fast though so it may not matter that much.