What does the syntax [::-1] really mean?

C

Casey

I've used [::-1] as a shorthand for reverse on several occasions, but
it occurred to me yesterday I never really thought about why it
works. First, I checked out the documentation.
From section 3.6 of the Python Library Reference:

"The slice of s from i to j with step k is defined as the sequence of
items with index x = i + n*k such that 0 <= n < (j-i)/k. In other
words, the indices are i, i+k, i+2*k, i+3*k and so on, stopping when j
is reached (but never including j). If i or j is greater than len(s),
use len(s). If i or j are omitted or None, they become ``end'' values
(which end depends on the sign of k). Note, k cannot be zero. If k is
None, it is treated like 1."
From Section 5.3.3 of the Python Language Reference (x[::-1] is a
"proper slice" in the BNF, hence the excerpt):

"The conversion of a proper slice is a slice object (see section 3.2)
whose start, stop and step attributes are the values of the
expressions given as lower bound, upper bound and stride,
respectively, substituting None for missing expressions."

Following the reference to section 3.2 provides a (non-rigorous)
description of what a slice object is, in terms of the extended
slicing semantics. But it doesn't shed any additional light on the
meaning of [::-1].
From this, I would expect that x[::-1] would be identical to x[n:0:-1]
(n and 0 being the "end" values, with the order switched due to the
negative step value). But the clause that "(but never including j)"
means that x[n:0:-1] excludes the 1st element of x, x[0]. A quick
test in ipython confirms that "abc"[3:0:-1] => "cb", not "cba".
Changing the "end" value to x[n:-1:-1] results in an empty string.

So my question is: "what exactly is [::-1] shorthand for"? Or is it a
special case, in which case why isn't it defined as such in the
library?
 
R

Robert Kern

Casey said:
I've used [::-1] as a shorthand for reverse on several occasions, but
it occurred to me yesterday I never really thought about why it
works. First, I checked out the documentation.
From section 3.6 of the Python Library Reference:

"The slice of s from i to j with step k is defined as the sequence of
items with index x = i + n*k such that 0 <= n < (j-i)/k. In other
words, the indices are i, i+k, i+2*k, i+3*k and so on, stopping when j
is reached (but never including j). If i or j is greater than len(s),
use len(s). If i or j are omitted or None, they become ``end'' values
(which end depends on the sign of k). Note, k cannot be zero. If k is
None, it is treated like 1."
From Section 5.3.3 of the Python Language Reference (x[::-1] is a
"proper slice" in the BNF, hence the excerpt):

"The conversion of a proper slice is a slice object (see section 3.2)
whose start, stop and step attributes are the values of the
expressions given as lower bound, upper bound and stride,
respectively, substituting None for missing expressions."

Following the reference to section 3.2 provides a (non-rigorous)
description of what a slice object is, in terms of the extended
slicing semantics. But it doesn't shed any additional light on the
meaning of [::-1].
From this, I would expect that x[::-1] would be identical to x[n:0:-1]
(n and 0 being the "end" values, with the order switched due to the
negative step value). But the clause that "(but never including j)"
means that x[n:0:-1] excludes the 1st element of x, x[0]. A quick
test in ipython confirms that "abc"[3:0:-1] => "cb", not "cba".
Changing the "end" value to x[n:-1:-1] results in an empty string.

So my question is: "what exactly is [::-1] shorthand for"? Or is it a
special case, in which case why isn't it defined as such in the
library?

It's a shorthand for [None:None:-1].

I think you're misinterpreting the sentence, "If i or j are omitted or None,
they become ``end'' values (which end depends on the sign of k)." The end values
*aren't* 0 and n except by happenstance. None (or omission) is a special marker
that says "go to the end".

--
Robert Kern

"I have come to believe that the whole world is an enigma, a harmless enigma
that is made terrible by our own mad attempt to interpret it as though it had
an underlying truth."
-- Umberto Eco
 
K

Kurt Smith

On 10/4/07 said:
Following the reference to section 3.2 provides a (non-rigorous)
description of what a slice object is, in terms of the extended
slicing semantics. But it doesn't shed any additional light on the
meaning of [::-1].
From this, I would expect that x[::-1] would be identical to x[n:0:-1]
(n and 0 being the "end" values, with the order switched due to the
negative step value). But the clause that "(but never including j)"
means that x[n:0:-1] excludes the 1st element of x, x[0]. A quick
test in ipython confirms that "abc"[3:0:-1] => "cb", not "cba".
Changing the "end" value to x[n:-1:-1] results in an empty string.

Check it out:

The second argument to the slice object, if negative, will convert
this index to (len(x)+j), which is why 'abc'[3:-1:-1] == 'abc'[2:2:-1]
== ''. If you pass in None for the 1st or second index, the slice
object will compute the right bounds based on the __len__ of the
object, and do the right thing. When there is no value specified,
None is assumed.

So my question is: "what exactly is [::-1] shorthand for"? Or is it a
special case, in which case why isn't it defined as such in the
library?

obj[::-1] == obj[None:None:-1] == obj[slice(None,None,-1)]
'abc'[::-1] 'cba'
'abc'[None:None:-1] 'cba'
'abc'[slice(None,None,-1)]
'cba'

Taking a look at the slice class:

| indices(...)
| S.indices(len) -> (start, stop, stride)
|
| Assuming a sequence of length len, calculate the start and stop
| indices, and the stride length of the extended slice described by
| S. Out of bounds indices are clipped in a manner consistent with the
| handling of normal slices.

Kurt
 
N

Neil Cerutti

I've used [::-1] as a shorthand for reverse on several occasions, but
it occurred to me yesterday I never really thought about why it
works. First, I checked out the documentation.
From section 3.6 of the Python Library Reference:

"The slice of s from i to j with step k is defined as the
sequence of items with index x = i + n*k such that 0 <= n <
(j-i)/k. In other words, the indices are i, i+k, i+2*k, i+3*k
and so on, stopping when j is reached (but never including j).
If i or j is greater than len(s), use len(s). If i or j are
omitted or None, they become ``end'' values (which end depends
on the sign of k). Note, k cannot be zero. If k is None, it is
treated like 1."

You just have to choose the right value for the end arguments.
With k negative, the result might be:

[-1:-len(s)-1:k]

I.e., from -1 (the last element) to one beyond the first element
(-len(s)-1). A negative index seems to be the only way to refer
to the element before the first element.

In that case the forumula given above does yield the reverse
sequence.

For example, for len(s) of 3, you get, you would get the
following sequence of indices:

i = -1, j = -4, k = -1

-1 + -1 = -2
-1 + 2*(-1) = -3
-1 + 3*(-1) = -4

-4 is equal to j, so the sequence ends with s[-3].
From this, I would expect that x[::-1] would be identical to
x[n:0:-1] (n and 0 being the "end" values, with the order
switched due to the negative step value). But the clause that
"(but never including j)" means that x[n:0:-1] excludes the 1st
element of x, x[0]. A quick test in ipython confirms that
"abc"[3:0:-1] => "cb", not "cba". Changing the "end" value to
x[n:-1:-1] results in an empty string.

Your proposed sequence, x[n:0:-1] is half-open on the wrong end.
When k is -1 then j must also be negative. This is because
there's no way to refer to the element one before the first
element without using a negative index value for j.
 
D

Duncan Booth

Casey said:
From this, I would expect that x[::-1] would be identical to x[n:0:-1]
(n and 0 being the "end" values, with the order switched due to the
negative step value). But the clause that "(but never including j)"
means that x[n:0:-1] excludes the 1st element of x, x[0]. A quick
test in ipython confirms that "abc"[3:0:-1] => "cb", not "cba".
Changing the "end" value to x[n:-1:-1] results in an empty string.

So my question is: "what exactly is [::-1] shorthand for"? Or is it a
special case, in which case why isn't it defined as such in the
library?

You made a mistake thinking that the first omitted value maps to 'n': in
fact it maps to -1.

the invariant that you are looking for is that for all non-negative a, b:
x[a:b:1] reversed is x[-len(x)+b-1:-len(x)+a-1:-1]

x[::1] is equivalent to x[0:len(x):1]

therefore x[::-1] is the same as:
x[-len(x)+len(x)-1:-len(x)+0-1:-1]
which is just:
x[-1:-len(x)-1:-1]
 
D

Duncan Booth

Duncan Booth said:
the invariant that you are looking for is that for all non-negative a, b:
x[a:b:1] reversed is x[-len(x)+b-1:-len(x)+a-1:-1]
I should of course have said "all a, b in the range 0 <= a <= len(x) and 0
<= b <= len(x)".
 
C

Casey

Duncan Booth said:
the invariant that you are looking for is that for all non-negative a, b:
x[a:b:1] reversed is x[-len(x)+b-1:-len(x)+a-1:-1]

I should of course have said "all a, b in the range 0 <= a <= len(x) and 0
<= b <= len(x)".

Thanks, again! I figured it out from Fred's and your initial posts.
IM(ns)HO, this is non-intuitive.

I would expect that the reverse of x[i,j,k] would be x[j,i,-k]; eg;
x[0:len(x):1] would be x[len(x):0:-1]. This representation is little
more than syntactic sugar for x[y for y in range(i,j,k)], with a -1
adjustment to i and j if k is < 0 (due to the way range operates) and
with appropriate boundary checking on i and j. The default values for
i and j would become (0,len(x)), flopped for k < 0.

There may be a good (or not-so-good) reason this representation
wouldn't have worked or would have caused problems. Or maybe it is
just personal preference.
 
R

Robert Kern

Casey said:
Duncan Booth said:
the invariant that you are looking for is that for all non-negative a, b:
x[a:b:1] reversed is x[-len(x)+b-1:-len(x)+a-1:-1]
I should of course have said "all a, b in the range 0 <= a <= len(x) and 0
<= b <= len(x)".

Thanks, again! I figured it out from Fred's and your initial posts.
IM(ns)HO, this is non-intuitive.

I would expect that the reverse of x[i,j,k] would be x[j,i,-k]; eg;
x[0:len(x):1] would be x[len(x):0:-1]. This representation is little
more than syntactic sugar for x[y for y in range(i,j,k)], with a -1
adjustment to i and j if k is < 0 (due to the way range operates) and
with appropriate boundary checking on i and j. The default values for
i and j would become (0,len(x)), flopped for k < 0.

There may be a good (or not-so-good) reason this representation
wouldn't have worked or would have caused problems. Or maybe it is
just personal preference.

It would be inconsistent for the reasons that Neil gave.

--
Robert Kern

"I have come to believe that the whole world is an enigma, a harmless enigma
that is made terrible by our own mad attempt to interpret it as though it had
an underlying truth."
-- Umberto Eco
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top