9

I was surprised to read here that

The start and step arguments default to None

since it also says:

slice(start, stop, step=1)

Return a slice object representing the set of indices specified by range(start, stop, step).

So I expected the default argument value for the step parameter to be 1.

I know that slice(a, b, None) == slice(a, b, 1) returns False, but I am curious if slice(a, b, None) always returns the same slice as slice(a, b, 1), or if there is some example that I haven't been able to think of for which they will return different slices.

I couldn't find anything about this in the extensive post on slicing here

desertnaut
  • 57,590
  • 26
  • 140
  • 166
Joe
  • 662
  • 1
  • 7
  • 20
  • 3
    That is likely the difference between a defaulted argument and a default behaviour. `step` is processed as 1 when None but still stored as None (which is different tan 1) – Alain T. Aug 07 '23 at 17:22
  • 1
    Hmm, I think you have found a minor documentation bug. The correct signature for slice is `slice(start, stop[, step])`. – wim Aug 07 '23 at 17:24
  • 1
    Someone may implement a function or class which interprets the step differently and therefore needs to know if the slice was written as e. g. `1:5` or `1:5:1`. – Michael Butscher Aug 07 '23 at 17:56
  • ah of course. silly me – juanpa.arrivillaga Aug 07 '23 at 17:58
  • 1
    I've added https://github.com/python/cpython/pull/107756 for the docs bug. – wim Aug 08 '23 at 01:15
  • @wim there are other things that I find a little confusing about that section of the docs, but I don't know if it's just me. For example, when it states "Return a slice object representing the set of indices specified by 'range(start, stop, step)'", I believe that's only true for positive values. For example, `slice(-1,0,1)` doesn't see to represent the same indices as specified by `range(-1,0,1)`. Although, that is already discussed [here](https://stackoverflow.com/q/48776238/6394617) – Joe Aug 08 '23 at 16:01
  • 1
    @Joe I agree the range comparison is not very helpful/accurate. Personally, I just think of an instance `slice(x, y, z)` as what you receive in your `__getitem__` from the syntax `obj[x:y:z]`, and don't try to make any analogy with range. – wim Aug 08 '23 at 18:30

1 Answers1

7

Slice's step indeed defaults to None, but using step 1 and None should be equivalent for all practical purposes. That's because in the C code where the step is actually used, there are checks which transform None into 1 anyway:

int
PySlice_GetIndices(PyObject *_r, Py_ssize_t length,
                   Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step)
{
    PySliceObject *r = (PySliceObject*)_r;
    if (r->step == Py_None) {
        *step = 1;
    } ...    
}

And:

int
PySlice_Unpack(PyObject *_r,
               Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step)
{
    PySliceObject *r = (PySliceObject*)_r;
    ...
    if (r->step == Py_None) {
        *step = 1;
    }
    ...
}

If you're wondering why they don't just default to 1 instead, perhaps it's because users may still want to slice using None explicitly (e.g. L[1:2:None]), and/or to give 3rd-party types the opportunity to handle 1 and None differently in their __getitem__/__setitem__/__delitem__ implementation.

wim
  • 338,267
  • 99
  • 616
  • 750
  • _"for all practical purposes"_ so for what impractical purposes would it _not_ default to `1`? – OverLordGoldDragon Aug 07 '23 at 17:33
  • 2
    @OverLordGoldDragon Comparing slice instances for equality. – wim Aug 07 '23 at 17:34
  • Where is `PySlice_GetIndices` actually used? I'm assuming, by internal data structures, `list` etc? Because if you create `s = slice(1, 10)` and then `print(s.stop)` it will print `None`, so if you are writing your own data structure that wants to support slices, you have to explicitly handle the `None` – juanpa.arrivillaga Aug 07 '23 at 18:01
  • 1
    @juanpa.arrivillaga It's used by the `indices` method of slice instances. I think you meant `s.step` not `s.stop` ... but yeah, sure, if you're writing your own data structure you're not obliged to treat `step=None` and `step=1` as equivalent. In fact you're not even obliged to use integers (I implemented a class once which used slicing with strings to implement a certain functionality..) – wim Aug 07 '23 at 18:18
  • [`slice.indices`](https://docs.python.org/3/reference/datamodel.html#slice.indices), and looks like it's also part of the [stable ABI](https://github.com/python/cpython/blob/16c9415fba4972743f1944ebc44946e475e68bc4/Misc/stable_abi.toml#L1297-L1298) since 3.2 – wim Aug 07 '23 at 18:23
  • 1
    Ah, I was unaware of this method. I guess for a given length, then, `slice_object1.indices(some_length) == slice_object2.indices(some_length)` gives the desired semantics. – juanpa.arrivillaga Aug 07 '23 at 18:41