On Sun, Sep 27, 2020 at 4:29 AM Stefano Borini <stefano.bor...@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)
```
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}
```
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)
```
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)   {}
```
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[**{}]           ()       {}
```
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)
*Pronouns: he/him **(why is my pronoun here?)*
<http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/HNZNT6SICDJODBSKTAYKQFZHFQ5MZ7XT/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to