Skip to content

Slice confusing with negative stop and strides and the 0th element. #73068

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
hardkrash mannequin opened this issue Dec 6, 2016 · 6 comments
Open

Slice confusing with negative stop and strides and the 0th element. #73068

hardkrash mannequin opened this issue Dec 6, 2016 · 6 comments
Labels
3.7 (EOL) end of life docs Documentation in the Doc dir

Comments

@hardkrash
Copy link
Mannequin

hardkrash mannequin commented Dec 6, 2016

BPO 28882
Nosy @terryjreedy, @hardkrash, @vadmium, @MojoVampire

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2016-12-06.05:06:40.291>
labels = ['3.7', 'docs']
title = 'Slice confusing with negative stop and strides and the 0th element.'
updated_at = <Date 2016-12-10.00:28:35.180>
user = 'https://github.com/hardkrash'

bugs.python.org fields:

activity = <Date 2016-12-10.00:28:35.180>
actor = 'martin.panter'
assignee = 'docs@python'
closed = False
closed_date = None
closer = None
components = ['Documentation']
creation = <Date 2016-12-06.05:06:40.291>
creator = 'hardkrash'
dependencies = []
files = []
hgrepos = []
issue_num = 28882
keywords = []
message_count = 6.0
messages = ['282500', '282501', '282578', '282801', '282810', '282814']
nosy_count = 5.0
nosy_names = ['terry.reedy', 'hardkrash', 'docs@python', 'martin.panter', 'josh.r']
pr_nums = []
priority = 'normal'
resolution = None
stage = 'needs patch'
status = 'open'
superseder = None
type = None
url = 'https://bugs.python.org/issue28882'
versions = ['Python 3.5', 'Python 3.6', 'Python 3.7']

@hardkrash
Copy link
Mannequin Author

hardkrash mannequin commented Dec 6, 2016

The slicing and using inputed math is is necessary to provide a special case to make the code behave as expected.

Unless I'm missing something. Like we can't do this, as we loose negative indexing from the end of the file?

Let's take the following example, byte swapping 32bit integers.

a = [0,1,2,3,4,5,6,7]

print([a[x] for x in [slice(y+3, y-1 if y > 1 else None, -1) for y in range(0, len(a), 4)]])
[[], [7, 7, 6, 5]]

Catching my explicit case, I changed my code to:
print([a[x] for x in [slice(y+3, y-1 if y > 1 else None, -1) for y in range(0, len(a), 4)]])
[[3, 2, 1, 0], [7, 6, 5, 4]]

Life proceeds as I am explicit, but now I have a conditional check that is slowing me down...

It appears that -1 is being considered the last element in the set.
This was surprising, as I couldn't use simple math to byte swap, I needed to pass None instead of -1

It appears PySlice_GetIndices in file cpython/Objects/sliceobject.c always
adds length if stop < 0 regardless to start and step.

    if (r->stop == Py_None) {
        *stop = *step < 0 ? -1 : length;
    } else {
        if (!PyLong_Check(r->stop)) return -1;
        *stop = PyLong_AsSsize_t(r->stop);
        if (*stop < 0) *stop += length;   # <-- Issue here?
    }

It seems that there is some undocumented logic and behavioral decisions.

Was it explicitly decided that a negative stop and negative stride
e.g.
In [46]: a[3:0:-1]
Out[46]: [3, 2, 1]

In [47]: a[3:-1:-1]
Out[47]: []

Not [3,2,1,0] (My least surprising value...)

Because -1 is based on len(a).

I expected that with a positive start, and a negative stride that the -1 case would be considered include 0.

In other code...
[4:-1:-1] == [4:None:-1]
Not
[4:-1:-1] == [4:len(a)-1:-1]
Especially when len(a)-1 > start

I understand that this is behavioral, but it is confusing...

Discussion?

@hardkrash hardkrash mannequin added the type-bug An unexpected behavior, bug, or error label Dec 6, 2016
@hardkrash
Copy link
Mannequin Author

hardkrash mannequin commented Dec 6, 2016

Argh, I swear I proofread this...

print([a[x] for x in [slice(y+3, y-1, -1) for y in range(0, len(a), 4)]])
[[], [7, 7, 6, 5]]

Catching my explicit case, I changed my code to:
print([a[x] for x in [slice(y+3, y-1 if y > 1 else None, -1) for y in range(0, len(a), 4)]])
[[3, 2, 1, 0], [7, 6, 5, 4]]

Also, I could have done this...

print(list(reversed([a[x] for x in [slice(y, y-4, -1) for y in range(-1, -len(a), -4)]])))
[[3, 2, 1, 0], [7, 6, 5, 4]]
But, Yikes All those inverses!

Side Note
I wish we had partitioning in ranges/slices.

a[::4] == [0, 4]
a[::4:2] == [[0, 1], [4, 5]]
a[::4:-4] == [[3, 2, 1, 0], [7, 6, 5, 4]]

@MojoVampire
Copy link
Mannequin

MojoVampire mannequin commented Dec 7, 2016

I find this report nigh incomprehensible, but I will admit that I can't seem to find any good explanations of the extended slicing rules in the Python docs.

The tutorial covers slicing, but, AFAICT, it never mentions extended slicing at all, not in 3.1.2 or 3.1.3 (Strings and Lists introduction), nor anywhere in section 5 (Data structures). 3.1.2 and 3.1.3 even lie a little (claiming the implicit start is always 0, and the implicit end is always len(sequence), when those reverse for a negative slice step).

The slice object and the slice glossary entry doesn't cover it either, nor link to anything that does. The Data model entry for Slicings doesn't seem to describe meaning either.

@terryjreedy
Copy link
Member

Steven, your initial 'sentence' is missing something needed to make it a proper English sentence, and I cannot exactly guess what you meant. But your later confusion is clear enough.

This tracker is for patching the Python docs and CPython code. I believe the entry for the slice function/class, https://docs.python.org/3/library/functions.html#slice, could be improved.

First the word 'slice' is linked to the Glossary entry for 'slice'. However, the latter is about subsequences, not about slice objects. It should instead link to the entry that contains the method description as https://docs.python.org/3/reference/datamodel.html#slice.indices. (This is the target for the Index entry 'indices (slice method)'. The 'slick objects' label needs to be made a target.

Second, the description "representing the set of indices specified by range(start, stop, step)" is only true when start and stop are valid non-negative indexes when applied to a particular sequence. It is never true for negative values and not true when greater to or equal to the length of a particular sequence. As the slice.indices entry says, 'Missing or out-of-bounds indices are handled in a manner consistent with regular slices."

In the example above, the issue is the negative stop, not the negative step. Steven's problem was expecting the description to be true (whether or not he read it.) It is worth noting, nowever, that reversed() was added because people had trouble using negative steps. The following is much clearer than the corrected code above that the object is to separately reverse two haves of a list.

>>> n = 8
>>> a = range(8)
>>> [list(reversed(a[:n//2])), list(reversed(a[n//2:]))]
[[3, 2, 1, 0], [7, 6, 5, 4]]

As the Library Function entry hints, slice() is mainly intended for use internally and by 3rd-party extensions.

The description is even more wrong because the three arguments/attributes 'can have any type' (from the Data model slice object entry) whereas range arguments must be int-like. (Allowing non-int-like arguments is for external extension use.) In particular, slice(None) == slice(None, None) == slice(None, None, None) 'represents' all the indices of a particular sequence, in normal order, whereas None is not a valid argument for range().

I am not sure what replacement to propose. I'd like to see if there is any previous discussion on the tracker.

A third and minor issue is that 'Numerical Python' should? be updated to "Numpy'.

@terryjreedy terryjreedy added 3.7 (EOL) end of life docs Documentation in the Doc dir labels Dec 9, 2016
@terryjreedy terryjreedy changed the title RFC: Slice confusing with negative strides and the 0th element. Slice confusing with negative stop and strides and the 0th element. Dec 9, 2016
@terryjreedy terryjreedy removed the type-bug An unexpected behavior, bug, or error label Dec 9, 2016
@vadmium
Copy link
Member

vadmium commented Dec 9, 2016

I think Steven’s main complaint is that it is hard to make a reversed slice extend to the start of the original sequence, unless you omit (or use None as) the endpoint:

>>> "01234567"[4:0:-1]  # Includes index [4], stops before reaching index [0]
'4321'
>>> "01234567"[3::-1]  # Includes index [3], stop omitted for start of string
'3210'
>>> "01234567"[3:None:-1]  # None means the same
'3210'
>>> "01234567"[3:-1:-1]  # Negative means len(...) - 1, i.e. index [7]
''

This is a consequence of (a) the stop parameter meaning “stop _before_ reaching this index”, and (b) negative indexes are interpreted specially as being relative to the end.

@vadmium
Copy link
Member

vadmium commented Dec 10, 2016

See also bpo-11842 about the behaviour of slice.indices() in this situation, and bpo-1446619 about fixing the documentation for reverse slices regarding positive out-of-range indexes.

@ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.7 (EOL) end of life docs Documentation in the Doc dir
Projects
None yet
Development

No branches or pull requests

2 participants