On Fri, Aug 7, 2020 at 10:47 PM Steven D'Aprano <st...@pearwood.info> wrote:

> On Fri, Aug 07, 2020 at 12:09:28PM -0400, Ricky Teachey wrote:
>
> > I was actually trying to help the kwd arg case here. As illustrated by
> the
> > quote I included from Greg Ewing, there seems to be not even close to a
> > consensus over what the semantic meaning of this should be:
> >
> > m[1, 2, a=3, b=2]
>
> This is Python-Ideas. If we asked for a consensus on what the print
> function should do, I'm sure we would find at least one person
> seriously insist that it ought to erase your hard drive *wink*
>

Yeah I get it. Thanks. I just noticed it didn't seem like anything close to
a consensus was even sort of rising to the surface.


> By the way, I assume you meant `__getitem__` in each of your examples,
> since `__get__` is part of the descriptor protocol.
>

Whoops thanks for the correction. And thanks for being less grumpy.

And thank you also for going through the 4 options I gave. I accept and
agree with you on all of them.

But I had forgotten the fifth.

The semantic meaning of m[1, 2, a=3, b=2] might be made to mean:

5.    m.__getx__(1, 2, a=3, b=4)

...which would in turn call, by default:

m.__getitem__((1, 2), a=3, b=4)

I was a little bit more detailed about in my first message so I'll quote
that:


> -----------
>
> One idea: change the "real" names of the dunders. Give `type` default
> versions of the new dunders that direct the call to the old dunder names.
>
> The new get and del dunders would have behavior and signatures like (I am
> including **__kwargs since that could be an option in the future) :
>
> def __getx__(self, /, *__key, **__kwargs):
>     return self.__getitem__(__key, **__kwargs)
>
> def __delx__(self, /,, *__key, **__kwargs):
>     del self.__delitem__(__key, **__kwargs)
>
> However the set dunder signature would be a problem, because to mirror the
> current behavior we end up writing what is now a syntax error:
>
> def __setx__(self, /, *__key, __value, **__kwargs):
>     self.__setitem__(__key, __value, **__kwargs)
>

When overriding `__getx__` et al, you would always call super() on the
__getx__ method, never use super().__getitem__:

class My:
    def __getx__(self, my_arg, *args, my_kwarg, **kwargs):
        # the way I have written things this super call will cause a
recursion error
        v = super().__getx__(*args, **kwargs)
        return combine(my_arg, my_kwarg, v)

Many of the advantages are shared with #1 as you recounted them:

(1) Existing positional only subscripting does not have to change for any
existing code (backwards
compatible).

(2) Easy to handle keyword arguments.

(3) Those who want to bundle all their keywords into a single object can
just define a single `**kw` parameter.

(4) Probably requires little special handling in the interpreter?

(5) Requires no extra effort for developers who don't need or want
keyword parameters in their subscript methods. Just do nothing.

(6). Consistency with other methods and functions for those that want to
use the new dunders.

Disadvantages:

(1) Probably requires more implementation effort than #1

(2) Similar to #2 will also create a long transition period- but hopefully
quite
a bit less painful than just outright changing the signature of __getitem__
etc. However libraries do not have to support both calling conventions
at all. They should be encouraged to start using the new one, but the
old one will continue to work, perhaps perpetually. But maybe things
would eventually get to the point that it could be eventually done away
with.

(3) Creates a new "kind of screwy" (Greg's words) situation that
will at times need to be explained and understood.

(4) Creates a sort of dual MRO for square bracket usage. You would end up
with situations like this:

class A:
    def __getitem__(self, key, **kwargs):
        print("A")

class B(A):
    def __getitem__(self, key, **kwargs):
        print("B")
        super().__getitem__(key, **kwargs)

class C(B):
    def __getx__(self, *key, **kwargs):
        print("C")
        super().__getx__(*key, **kwargs)

class D(C):
    def __getx__(self, *key, **kwargs):
        print("D")
        super().__getx__(*key, **kwargs)

>>> D()[None]
D
C
B
A

This code obviously looks just a little bit odd but the result is fine.

However when different libraries and classes start getting mashed together
over time, you might end up with a situation like this:

class A:
    def __getitem__(self, key, **kwargs):
        print("A")

class B(A):
    def __getx__(self, key, **kwargs):
        print("B")
        super().__getx__(*key, **kwargs)

class C(B):
    def __getitem__(self, key, **kwargs):
        print("C")
        super().__getitem__(key, **kwargs)

class D(C):
    def __getx__(self, *key, **kwargs):
        print("D")
        super().__getx__(*key, **kwargs)

>>> D()[None]
D
B
C
A

Seems like this could be easily fixed with a class decorator, or perhaps
the language could know to call the methods in the right order (which
should be possible so long as the rule to never do a super().__getitem__
call inside of the new __getx__ method, and vice versa: don't
super()__getx__ from a __getitem__ method_, is followed).

Also Steven please ignore the messages I accidentally sent just to your
email; apologies.
_______________________________________________
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/2VA2UDGVCYW3KD7BPNQH6GLYLRJZPTO3/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to