
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) ``` 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-c...>