On Sat, Aug 29, 2020 at 11:04 AM Jonathan Fine <jfine2358@gmail.com> wrote: 
[snip]
The passage
    >>> d[1, 2] = 'val'
    >>> d.__setitem__((1,2), 'val')
goes via tuple. If tuple didn't already exist, we'd have to invent it. And so here tuple is what I call "the key function".

IIUC in order to get these semantics under your proposed system, I should either leave __keyfn__ unset (for backward compatible behavior) or set it explicitly to True. Is that correct?

The key function is the function that is the intermediary between
    >>> d[EXPRESSION]
    >>> d.__getitem__(key)

When there is a key function, the signatures are
    __getitem__(key)
    __delitem__(key)
    __setitem__(key, val)

So we're allowed to have
    class A:
        __keyfn__ = None
        def __setitem__(self, val, a, b, c d):
            # set the value

Recall that dict has, implicitly, a way of getting a key from permitted arguments. The meaning of
    class B:
         __keyfn__ = True
        def __getitem__(key):
            # Get the value
is that the key is produced by exactly the same method as in dict.

The meaning of
    __keyfunc__ = True
is "produce a key from the arguments, in exactly the same way as in dict". Or in other words, "Yes, it's True. We do have a keyfn. Use the default, dict, keyfn."

I think (None, True) works better than (False, True). This is because a further PEP might allow the user to supply a custom keyfn. So while it is at this time a binary choice, there might be further choices in future.
 
Can you explain (in words or with examples) what happens in each case? You were so excited to show off one case that you never showed how the other case would work

How about:

    class A:
        __keyfn__ = None
        def __setitem__(self, val, x=0, y=0, z=0):
            print((val, x, y, z))

Okay, I am beginning to understand your proposal (despite vehemently disagreeing). You propose that setting __keyfn__ = None should change the signature of __setitem__ so that

1. the value is placed first (before the "key" values)
2. the rest of the arguments (whether positional or keywords) are passed the same way as for a function
 
    >>> a = A()
    >>> a[1, z=2] = 'hello'
    ('hello', 1, 0, 2)



And also

    class C:
        __keyfn__ = True
        def __setitem__(self, *argv, **kwargs):
            print(f'argv={argv} | kwargs={kwargs}')

    >>> c = C()

    >>> c[1] = 'val'
    argv=(1, 'val') | kwargs={}
    >>> c[1, 2] = 'val'
    argv=((1, 2), 'val') | kwargs={}

    >>> c[a=1] = 'val'
    TypeError: __keyfn__ got unexpected keyword argument 'a'


I hope this helps.

Yes. I find it a big flaw that the signature of __setitem__ is so strongly influenced by the value of __keyfunc__. For example, a static type checker (since PEP 484 I care deeply about those and they're popping up like mushrooms :-) would have to hard-code a special case for this, because there really is nothing else in Python where the signature of a dunder depends on the value of another dunder.

And in case you don't care about static type checkers, I think it's the same for human readers. Whenever I see a __setitem__ function I must look everywhere else in the class (and in all its base classes) for a __keyfn__ before I can understand how the __setitem__ function's signature is mapped from the d[...] notation.

Finally, I am unsure how you would deal with the difference between d[1] and d[1,], which must be preserved (for __keyfn__ = True or absent, for backwards compatibility). The bytecode compiler cannot assume to know the value of __keyfn__ (because d could be defined in another module or could be an instance of one of several classes defined in the current module). (I think this problem is also present in the __subscript__ version.)

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