
On Fri, Sep 25, 2020 at 11:50 PM Steven D'Aprano <steve@pearwood.info> wrote:
TL;DR:
1. We have to pass a sentinel to the setitem dunder if there is no positional index passed. What should that sentinel be?
Isn't there a problem with this starting assumption? Say that I have item dunder code of the signature that is common today, and I have no interest in providing direct support for kwd args in my item dunders. Say also, kwd unpacking is supported. If a person unpacks an empty dictionary (something that surely will happen occasionally), and a SENTIAL is provided, this poses a pretty good possibility to lead to unintended behavior in many cases: class C: def __getitem__(self, index): ... d = {} c = C() c[**d] The above will call: c.__getitem__(SENTINEL) Who knows what the effect of c[SENTINEL] will be for so much existing code out there? Bugs are surely to be created, aren't they? So a lot of people will have to make changes to existing code to handle this with a sentinel. This seems like a big ugly problem to me that needs to be avoided. There's a third option: just prohibit keyword-only subscripts. I think
that's harsh, throwing the baby out with the bathwater. I personally have use-cases where I would use keyword-only subscripts so I would prefer options 1 or 2.
Maybe there's a middle way option that could avoid the particular problem outlined above: # Option 4: do use a sentinel at all, user provides own (if desired) obj[spam=1, eggs=2] # => calls type(obj).__getitem__(USER_PROVIDED_SENTINEL, spam=1, eggs=2) del obj[spam=1, eggs=2] # => calls type(obj).__delitem__(USER_PROVIDED_SENTINEL, spam=1, eggs=2) obj[spam=1, eggs=2] = value # => calls type(obj).__setitem__(USER_PROVIDED_SENTINEL, value, spam=1, eggs=2) How do we write the dunder item to allow for this? Simple: just require that if it is desired to support kwd-only item setting, the writer of the code has to provide a default-- ANY default-- to the value argument in setitem (and this default will never be used). I suggest the ellipses object as the standard convention: MyPreferredSentinel = object() class C: def __setitem__(self, index=MyPreferredSentinel, value=..., **kwargs): ... c=C() c[x=1] = "foo" # calls => c.__setitem__(MyPreferredSentinel, "foo", x=1) This option provides an additional benefit: if I don't want to provide support for the kwd-only case, I don't have to handle it explicitly-- errors occur without any effort on my part, just as they do today (although the error itself is different): class C: def __setitem__(self, index, value, **kwargs): ... c = C() c[x=1] = "foo" # yay, this produces an error with no effort Providing a default sentinel nullifies this possibility. In that case, have to handle a kwd-only indexing myself: class C: def __setitem__(self, index, value, **kwargs): if index is SENTINEL: handle_kwd_args_only() --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler