On Wed, Sep 2, 2020 at 4:23 AM Steven D'Aprano <steve@pearwood.info> wrote:
On Wed, Sep 02, 2020 at 12:55:43PM +1200, Greg Ewing wrote:
> On 2/09/20 3:44 am, Steven D'Aprano wrote:
> >(9) Keyword-only subscripts are permitted:
> >
> >     obj[spam=1, eggs=2]
> >     # calls type(obj).__getitem__(spam=1, eggs=2)
> >
> >     del obj[spam=1, eggs=2]
> >     # calls type(obj).__delitem__(spam=1, eggs=2)
>
> An alternative is to pass an empty tuple as the index when there
> are no positional args. That would make the following less of a
> special case:

I think that will interfere with people who need to choose their own
default for the index.

Unifying the behaviour of the getter and the setter only makes sense if
you have both; for immutable objects (hence no setter) that need only
keyword subscripts, why bother declaring an index you're never going to
use when you actually want this signature?

    __getitem__(self, *, keyword=default)

Having to declare a positional index parameter I don't want, simply for
consistency with a setitem I'm not even using, might be a tad annoying.

The whole backwards compatibility thing is more than a bit annoying, isn't it?
 
So maybe accepting that you'll always get a key, and it may be `()`, isn't the worst part.

OTOH, maybe we can cook up a scheme where we have two API families, `__getitem__/__setitem__` and `__getindex__/__setindex__`. The signature of `__setindex__` has the value first (after `self`).

The rules could be something like

- If keywords are present, always use `__getindex__/__setindex__` (fail if they aren't present); if multiple positional indices are present, these become multiple arguments.

- Otherwise, if only `__getindex__/__setindex__` are present, call those, and lose the distinction between `d[1]` and `d[1,]`; `d[1, 2]` becomes two positional arguments.

- Otherwise, if only `__getitem__/__setitem__` are present, call those in a 100% backwards compatible way.

- Otherwise, if both are present, call `__getitem__/__setitem__` if no outer-level comma is present (so for `d[1]` as well as for `d[(1, 2)]`), and call `__getindex__/__setindex__` if an outer-level comma is present (so for `d[1,]` as well as for `d[1, 2]`).

This can be done relatively cleanly with changes at both the bytecode level and the C API level.

I'm not sure I like this better than the "pure `__getitem__/__setitem__` scheme, because the API duplication is troublesome,

--
--Guido van Rossum (python.org/~guido)