[Python-ideas] Where did we go wrong with negative stride?

אלעזר elazarg at gmail.com
Fri Nov 1 13:15:23 CET 2013


2013/11/1 Steven D'Aprano <steve at pearwood.info>:
> On Thu, Oct 31, 2013 at 03:37:36PM +0200, אלעזר wrote:
>> Note that with an End object (regardless of wheather it's called
>> "End", "len-x" or ":-x") we can get End/5. I think that's a nice thing
>> to have.
>
> I presume that you expect a[End/5] to be equivalent to a[len(a)//5] ?
>
Yes. Perhaps End//5 is better of course.

>
>> One more thing: End-5 should be callable, so it can be passed around.
>>
>> (End-3)("hello") == len("hello")-3
>> (End-0)("hello") == len("hello")
>>
>> This way End is a generalization of len, making len somewhat redundant.
>
> All this seems very clever, but as far as I'm concerned, it's too
> clever. I don't like objects which are context-sensitive. Given:
>
>     x = End-2
>
> then in this context, x behaves like 4:
>
>     "abcdef"[x:]
>
> while in this context, x behaves like 0:
>
>     "abcd"[x:]
>
Sure you mean  "x behaves like 2":

> I really don't like that. That makes it hard to reason about code.

Well, look at that:

>>> x=-2
>>> "abcdef"[x:] # x behaves like 4:
'ef'
>>> "abcd"[x:]  # x behaves like 2:
'cd'

Magic indeed. Not to mention what happens when it happens to be x=-0

Unlike ordinary int, where -x is an abbreviation for 0-x, so -0 == 0-0
== 0, in the context of slicing and element access (in a list, not in
a dict) -x is an abbreviation for len(this list)-x, so -0 == len(this
list)-0 != 0. Well, End is len(this list), or equivalently it's "0
(mod len this list)". I think it's natural.

> End seems to me to be an extremely sophisticated object, far too
> sophisticated for slicing, which really ought to be conceptually and
> practically a simple operation. I would not have to like to explain this
> to beginners to Python. I especially would not like to explain how it
> works. (How would it work?)
say lst[:end-0] become lst[slice(end-0), and inside list you take
stop(self) and continue from there as before. (Turns out slice does
not take keyword arguments. Why is that?) There are other ways, of
course. Which makes it possible to pass any other callable, unless you
want to explicitly forbid it. I agree that this:

     "abcdef"[1:(lambda lst: lst[0])]

is a horrible idea.

The reason it is hard to explain is that you don't want to explain
functional programming to a beginner. But if the End object (which I
don't think is "extremely" sophisticated) will be accessible only from
within a slice expression, all you have to explain is that it is
context-dependent, regardless of how it is actually implemented. It's
just a readable "$", if you like. Slice notation is already a domain
specific sublanguage of its own right; for example, nowhere else is
x:y:z legal at all, and nowhere else does -x have a similar meaning.

As for the negative stride, I would suggest a terminating minus for
"reverse=True" or Nick's rslice. Possibly with a preceding comma:

     "abcdef"[1:-1 -] == "edcb"
     "abcdef"[-] == "fedcba"
     "abcdef"[1:end-1:2, -] ==  ''.join(reversed("abcdef"[1:-1:2])) == "db"

(I don't understand why can't we have
    -[1,2,3] == list(reversed([1,2,3]))
I'm sure that's a conscious decision but is it documented anywhere?
Yes, it's not exactly a negation, but then 'ab'+'cd' is not exactly an
addition - it is not commutative. nor does it have an inverse. It's
just an intuitive notation, and I think "xyz" == -"zyx" is pretty
intuitive; much more so than "zyx"[::-1], although not much more than
"xyz"[-].
Taking it one step further, you can have things like

     "abcde"[-(1:3)] == "abcde"[-slice(1,3)] == "cb"

Again, this look intuitive to me. I admit that my niece failed to
guess this meaning so I might be wrong; She is completely unfamiliar
with Python though)

Elazar


More information about the Python-ideas mailing list