Note that __lcontains__ (if it exists) would be called first, at least for
different types. So maybe it would be easier than you think. But I still
think it’s not needed.

On Tue, Nov 12, 2019 at 9:04 PM Andrew Barnert via Python-ideas <
python-ideas@python.org> wrote:

> On Nov 12, 2019, at 17:00, Samuel Muldoon <muldoonsam...@gmail.com> wrote:
>
> *Currently, the `in` operator (also known as `__contains__`) always uses
> the rightmost argument's implementation.*
>
> *For example,*
>
>
>> *   status = obj in "xylophone" *
>>
>
> *Is similar to:*
>
> *    status = "xylophone".__contains__( obj )*
>
>
> *The current implementation of  `__contains__` is similar to the way that
> `+` used to only look to the leftmost argument for implementation. *
>
> *    total = 4 + obj*
>>
>> *    total = int.__add__(4, obj)*
>>
>
> When was this? I’m pretty sure __radd__ was there in 1.x.
>
> *However, these days, `__radd__` gives us the following:*
>
> *     try:*
>> *         total = type(4).__add__(4, obj)*
>> *     except NotImplementedError:*
>> *         total = type(obj).__radd__(obj, 4) *
>>
>
> *We propose something similar for `__contains__`: That a new dunder/magic
> method `__lcontains__` be created and that the `in` operator be implemented
> similarly to the following:*
>
> *    # IMPLEMENTATION OF*
>>
>> *    #     status = obj in "xylophone"`*
>> *    try:*
>> *        status =  "xylophone".__contains__(obj)*
>> *    except NotImplementedError:*
>>
>> *        status = False *
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>> *    if not status:        try:            status =
>> obj.__lcontains__(“xylophone”)    except AttributeError:        # type(obj)
>> does not have an `__lcontains__` method        with io.StringIO() as
>> string_stream:            print(                "unsupported operand
>> type(s) for `in`:",                repr(type(4).__name__),
>> "and",                repr(type(obj).__name__),
>> file=string_stream            )            msg = string_stream.getvalue()
>>       raise TypeError(msg) from None*
>>
>
> You’ve specified rules that are different from the one you gave for
> __radd__, and also different from the actual rules for __radd__. Is that
> intentional? If so, why?
>
> To summarize the rules: If type(rhs) is a proper subclass of type(lhs),
> check rhs.__radd__ first and fall back to lhs.__add__. Otherwise, if
> they’re the same type, only check lhs.__add__. Otherwise, check lhs.__add__
> first and fall back to rhs.__radd__. In each case, the check uses special
> method lookup, not normal getattr. Also, fallback happens if lookup raises
> an AttributeError or the call returns NotImplemented; it does not happen if
> either one raises NotImplementedError. And finally, if the fallback fails
> in the same way, you get a TypeError.
>
> *As an example application, one might develope a tree in which each node
> represents a string (the strings being unique within the tree). A property
> of the tree might be that node `n` is a descendant of node `m` if and only
> if `n` is a sub-string of `m`. For example the string "yell" is a
> descendant of "yellow." We might want the root node of the tree to be a
> special object, `root` such that every string is in `root` and that `root`
> is in no string.*
>
>
> I don’t understand why you’d want this. If your tree is defined as
> substrings of a string, why isn’t your root the maximal string, instead of
> an empty string? Also, why does `node in “yellow”` work in the first place,
> when “yellow” is a str, not a Node?  Also, any string is a substring of
> itself; do you actually want every Node to be a descendant of itself? (And,
> if so, is the root a descendant of itself or not?) And finally, doesn’t
> this mean the root of any tree contains every descendant of every possible
> tree, not just its own descendants?
>
> Most of all, why can’t you implement your rule in Python today, without
> any new methods?
>
>     class Node:
>         def __contains__(self, other):
>             if self.isroot: return True
>             if other.isroot: return False
>             return other.label in self.label
>
> The only reason you need __radd__ is to handle interaction with different
> types, especially ones you don’t control. When you’re just building a
> single type, you can put all the logic in __add__. And the same thing ought
> to be true for __lcontains__.
>
> Not understanding the point of this example makes it hard to evaluate how
> well the proposal solves it, but I don’t think it actually does.
>
> * That is, the code `root in "yellow"` should return `False`. If `
> __lcontains__ ` were implemented, then we could implement the node as
> follows:*
>
>>
>>
>> *class RootNode(Node): *
>>
>> *    def __contains__(container, element):*
>>
>>
>> *        return True *
>>
>> *    def __lcontains__(element, container):*
>>
>> *        return False*
>>
>
> Presumably the rhs’s __contains__ method exists and does not raise
> NotImplementedError, right? Then by your rules, RootNode.__lcontains__
> would never get called.
>
> This is the reason for those complicated rules about proper subclasses,
> identical classes, and unrelated classes being handled differently by
> __radd__. But even with those rules, your rhs isn’t even a Node, it’s a
> str. And str.__contains__ definitely exists and doesn’t raise
> NotImplementedError, and, as it’s an unrelated class, it will get called
> first, so you’ll just get a TypeError without ever having the chance to get
> your __lcontains__ called.
>
> And that means that you can’t actually get the benefits without massively
> breaking backward compatibility. The only reason you can use __radd__ to
> make new types be addable to int is that int.__add__ doesn’t raise
> TypeError on unknown types, it returns NotImplemented. And the same for
> every other builtin, stdlib, and third-party numeric type. But every
> builtin, stdlib, and third-party container type raises TypeError from
> __contains__ on unknown types. So for __lcontains__ to be useful, they’d
> all have to be changed to return NotImplemented instead.
>
> I think __lcontains__ (following the same rules as __radd__, and with the
> change to every existing __contains__, and probably at least two versions’
> worth of __future__) could be useful, and if I were designing a new
> Python-like language I’d probably include it unless someone came up with a
> good reason not to. By adding it today would definitely be disruptive. So
> it needs a real killer use case that’s worth all that disruption.
>
> _______________________________________________
> 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/OGE6RWUPGVNZFTCW2OAXQWQKSMLPHC6U/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
-- 
--Guido (mobile)
_______________________________________________
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/3OTDAQLX2VDPS7YJDAN65U4ER774X5EZ/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to