On Sun, Sep 27, 2020 at 4:29 AM Stefano Borini <stefano.borini@gmail.com> wrote:

```

>>> obj[**d] = "foo" # no kwd arguments provided here

```

I committed yesterday the following proposal

https://github.com/python/peps/pull/1622

But to be honest I am not sure if we should disallow these two constructs

```

d[*()]

d[**{}]

```

as equivalent to the disallowed `d[]` or allow them as equivalent to

`d[()]` (or whatever the sentinel will be)

I have thought extensively about this issue (in fact I lie awake thinking it through last night :-) and have come to some kind of resolution.

TL;DR: these should be allowed and use `d[()]` or whatever the sentinel will be, but I prefer the sentinel to be `()`.

But there are more cases than just the above two. Below I try to catch them all.

We've already established that in the absence of `*a` and keyword args, the index is a tuple when it looks like a tuple:

```

SYNTAX INDEX

d[x] x

d[x,] (x,)

d[x, y] (x, y)

d[x] x

d[x,] (x,)

d[x, y] (x, y)

```

We've also established that in the absence of `*a` but with at least one keyword arg, the index is a tuple unless there is exactly one index value:

```

SYNTAX INDEX KWARGS

d[x, k=z] x {"k": z}

d[x, y, k=z] (x, y) {"k": z}

d[x, k=z] x {"k": z}

d[x, y, k=z] (x, y) {"k": z}

```

I propose to treat `*a` in a similar fashion: If _after expansion of `*a`_ there is exactly one index value, the index is that value, otherwise it's a tuple. IOW:

```

SYNTAX INDEX

d[x, *[]] x

d[x, *[y]] (x, y)

d[*[], x] x

d[*[y], x] (y, x)

d[*[]] ()

d[*[x]] x

d[*[x, y]] (x, y)

d[x, *[]] x

d[x, *[y]] (x, y)

d[*[], x] x

d[*[y], x] (y, x)

d[*[]] ()

d[*[x]] x

d[*[x, y]] (x, y)

```

Note that I use `*[...]` instead of `*(...)` -- since `*a` takes an arbitrary iterable, it doesn't matter whether `a` is a list, tuple, another type of sequence, or a general iterable (e.g. a set or dict, or a generator). I'm using `*[...]` consistently just to remind us of this fact (and to avoid having to type a trailing comma to create a singleton tuple).

We can easily extend this scheme to keyword args: As soon as either a keyword argument or `**kwargs` (or both) is present, we apply the same rule: If _after expansion of `*a`_ there is exactly one index value, the index is that value, otherwise it's a tuple. I'm not going to show all the cases, but here are some examples:

```

SYNTAX INDEX KWARGS

d[*[], k=z] () {"k": z}

d[*[x], k=z] x {"k": z}

d[*[x, y], k=z] (x, y) {"k": z}

d[*[], **{}] () {}

d[*[x], **{}] x {}

d[*[x, y], **{}] (x, y) {}

SYNTAX INDEX KWARGS

d[*[], k=z] () {"k": z}

d[*[x], k=z] x {"k": z}

d[*[x, y], k=z] (x, y) {"k": z}

d[*[], **{}] () {}

d[*[x], **{}] x {}

d[*[x, y], **{}] (x, y) {}

```

I propose to then treat the cases where there are no positional index values, only keywords (either `k=1` or `**{...}`, even `**{}`) _as if preceded by `*[]`_. So:

```

SYNTAX INDEX KWARGS

d[k=z] () {"k": z}

d[**{"k": z} () {"k": z}

d[**{}] () {}

d[k=z] () {"k": z}

d[**{"k": z} () {"k": z}

d[**{}] () {}

```

The reason for these choices is to minimize the number of inconsistencies. We have a few unavoidable inconsistencies:

- if there's only one index value the index is not a tuple, in all other cases it's a tuple -- backward compatibility

- the special case for `d[x,]` (not the same as `d[x]`) -- also backward compatibility

- the difference between `d[x,]` (index is a tuple) and `d[x, k=1]` (index not a tuple) -- user expectations

There's also the special case for `d[k=1]`. Here our choices are to either forbid it syntactically or to provide a sentinel. I think forbidding it will prevent some reasonable use cases (e.g. tables with columns that have both positions and names). So I think it's best to use a sentinel. Using `()` as the sentinel reduces the number of special cases: the rule becomes "if there's exactly one positional value, use that value as the index; otherwise use a tuple", while with another sentinel the rule would become "if there's more than one positional value, use a tuple, if there's exactly one use that value, else (there are no positional values) use the sentinel". But if in the end people prefer a sentinel other than `()`, I can live with that -- in all the above cases, just replace `()` with the selected sentinel.

In both cases we also have an exception for the form `d[x,]`, but this factors out because it's the same complication in each case. Note that this exception is unique -- it only applies if the syntactic form has no keywords, no `**kwargs`, and no `*args`.

The introduction of `*args` requires us to decide what to do with the edge cases. I think the rule that best matches user expectations is to combine the plain positional values with the expansion of `*args`, take the resulting sequence of values, and _then_ apply the rule from the previous paragraph (using whichever sentinel we end up deciding on).

The introduction of `**kwargs` should pose no extra difficulties. Again, if there are no positional index values the sentinel index is used, and syntactic keywords are combined with `**kwargs` to form a single dict of keyword args. We end up finding that `d[**kwargs]` uses the index sentinel regardless of whether `kwargs` is empty or not. (If we were to end up forbidding `d[k=1]` syntactically, the only consistent choice would be to raise for `d[**{}]`, but I don't see a good reason to go this way.)

Note that `*args` and `**kwargs` both must combine the hard-coded arguments of the same nature (positional or keyword) with the dynamic ones before deciding. Anything else would lead to more rules and more inconsistencies.

Finally, in the above examples, `x`, `y` and `z`, when occurring in hard-coded arguments of either nature, may also be slices, e.g. `d[x]` could be `d[i:j]` or `d[i:j:k]` or e.g. `d[::]`. This makes no difference for the analysis. Note that in `*args` and `**kwargs` the slice notation is not syntactically valid -- but you can use explicit calls to `slice()`, e.g. `slice(i, j)`, `slice(i, j, k)` or `slice(None, None, None)`.

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