
I DO expect this thread to be bombarded with negative replies. Currently, there are `in`/`not in` operators which work like this in Python:
def contains(contains_value, iterable, not_in): for element in iterable: if element == contains_value: return True ^ not_in return False ^ not_in If I wanted to check if an *exact* object is in an iterable, I would have to loop like this (reverse boolean values for implementation of `not is in`): is_in = False for element in iterable: if element is contains_value: is_in = True I would want a more *convenient* way to check for this value. So therefore, there should be `is in`/`not is in` operators to do it better. Is this a valid reason?

On Mon, Oct 25, 2021 at 7:40 PM Jeremiah Vivian <nohackingofkrowten@gmail.com> wrote:
I DO expect this thread to be bombarded with negative replies.
Currently, there are `in`/`not in` operators which work like this in Python:
def contains(contains_value, iterable, not_in): for element in iterable: if element == contains_value: return True ^ not_in return False ^ not_in If I wanted to check if an *exact* object is in an iterable, I would have to loop like this (reverse boolean values for implementation of `not is in`): is_in = False for element in iterable: if element is contains_value: is_in = True I would want a more *convenient* way to check for this value. So therefore, there should be `is in`/`not is in` operators to do it better. Is this a valid reason?
What's your use-case? Can you give an example? Sometimes the solution is a different data structure, like an identidict. ChrisA

Something like this:
\>\>\> class Movement: ... def __eq__(self, x): ... return type(x) is Movement ... \>\>\> dummy = Movement() \>\>\> # suppose `bar` is a list of every recorded action in a game \>\>\> if dummy in bar: ... if dummy is in bar: # check if the dummy value is in the actions ... raise TypeError("cannot put dummy value in actions log")

On Mon, Oct 25, 2021 at 8:09 PM Jeremiah Vivian <nohackingofkrowten@gmail.com> wrote:
Something like this:
\>\>\> class Movement: ... def __eq__(self, x): ... return type(x) is Movement ...
Uhh, why are you defining equality in this bizarre way? Every Movement is equal to every other? ChrisA

On 2021-10-25 5:32 a.m., Jeremiah Vivian wrote:
For quick checking if a `Movement` object is inside of an iterable.
It seems the core of your problem is that you took the mechanism that's supposed to tell you if two objects are identical for another purpose, and now are complaining that you don't have a way to tell if two objects are identical. The simple and obvious solution is not to add a way to do __contains__ checks that bypass __eq__, it's to take out your __eq__ method (you seem to be satisfied with an identity check, but if not, you can give it a proper equality check instead), and to replace your misguided `Movement() in container` checks with a simple utility method:
def has_movement(container): ... return any(isinstance(item, Movement) for item in container)

On Mon, Oct 25, 2021 at 08:39:19AM -0000, Jeremiah Vivian wrote:
If I wanted to check if an *exact* object is in an iterable
A nice way to check for exact identity in an iterable is this: any(value is element for element in iterable) That stops on the first match, and is pretty efficient. To reverse the check, "not in", use the obvious `not any(...)` as above. "element is in iterable" reads nicely, but the difference between that and "element in iterable" is subtle and problematic. Especially for English speakers, where "x in y" is strictly speaking grammatically incorrect: Wrong: if George in Europe, send him an email Right: if George is in Europe, send him an email I'm surely not the only one who occassionally puts in an unwanted `is` into `in` tests. Fortunately that is a syntax error now. Otherwise, it would silently do the wrong thing. And then the coder who accidentally inserts an unneeded `is` into the test will have to deal with weird implementation-dependent silent failures due to caching of small ints and strings: 5 is in range(10) # may succeed in CPython, but fail in Jython 5000 is in range(4000, 6000) # will probably fail everywhere 5000 is in [4000, 5000, 6000] # may succeed in CPython x = 5000 x is in [4000, 5000, 6000] # may or may not succeed int('5000') is in [4000, 5000, 6000] # probably fail "a" is in "cat" # probably succeed in CPython "cat" is in "caterpiller" # definitely fail "CAT".lower() is in ['bat', 'cat', 'dog'] # possibly fail So although the syntax reads nicely, it would be a bug magnet. -- Steve

It should make sense that if an operation is grammatically correct in a programming language, there's something wrong there. There could be alternative syntax,
'is `object` in `iterable`' or 'is `object` not in `iterable`' but I feel like there's some disadvantage to this alternative syntax.

On Mon, Oct 25, 2021 at 8:35 PM Steven D'Aprano <steve@pearwood.info> wrote:
Otherwise, it would silently do the wrong thing. And then the coder who accidentally inserts an unneeded `is` into the test will have to deal with weird implementation-dependent silent failures due to caching of small ints and strings:
5 is in range(10) # may succeed in CPython, but fail in Jython
5000 is in range(4000, 6000) # will probably fail everywhere
5000 is in [4000, 5000, 6000] # may succeed in CPython
x = 5000 x is in [4000, 5000, 6000] # may or may not succeed
int('5000') is in [4000, 5000, 6000] # probably fail
"a" is in "cat" # probably succeed in CPython
"cat" is in "caterpiller" # definitely fail
"CAT".lower() is in ['bat', 'cat', 'dog'] # possibly fail
So although the syntax reads nicely, it would be a bug magnet.
It's worth noting that "in" is defined by the container. Object identity and equality aren't actually part of the definition. A lot of containers will behave as the OP describes, but strings, notably, do not - if you iterate over "caterpillar", you will never see "cat", yet it is most definitely contained. An "is in" operator is half way between being defined by the operator and defined by the container. This won't work for all containers. If you need that kind of thing a lot, it's not that hard to define a search object accordingly: class Is: def __init__(self, obj): self.obj = obj def __eq__(self, other): return self.obj is other
Is(x) in container
But this is a code smell. ChrisA

It's worth noting that "in" is defined by the container. Object identity and equality aren't actually part of the definition. A lot of containers will behave as the OP describes, but strings, notably, do not - if you iterate over "caterpillar", you will never see "cat", yet it is most definitely contained. I've been thinking of the `is in` operator using `in` when the iterable is just a single mass of items, like a string is just a single mass of characters. Is this a good idea?

On Mon, Oct 25, 2021 at 9:33 PM Jeremiah Vivian <nohackingofkrowten@gmail.com> wrote:
It's worth noting that "in" is defined by the container. Object identity and equality aren't actually part of the definition. A lot of containers will behave as the OP describes, but strings, notably, do not - if you iterate over "caterpillar", you will never see "cat", yet it is most definitely contained. I've been thinking of the `is in` operator using `in` when the iterable is just a single mass of items, like a string is just a single mass of characters. Is this a good idea?
The operator would exist regardless of what the container is, and it has to have some kind of semantic definition. Not all containers have a concept of equality/identity containment, so it's much better to stick to the existing operator and define your own container or search object with the semantics you want. ChrisA

All containers do have a concept of iterators though, and the `is in` operator can check using the iterator of the container.

On Thu, Oct 28, 2021 at 4:52 PM Jeremiah Vivian <nohackingofkrowten@gmail.com> wrote:
All containers do have a concept of iterators though, and the `is in` operator can check using the iterator of the container.
But the "in" operator isn't built on iteration, so that would be in-consistent. What you're asking for can best be spelled with any/all and iteration, not a new operator. ChrisA

On Thu, Oct 28, 2021 at 05:25:52PM +1100, Chris Angelico wrote:
But the "in" operator isn't built on iteration, so that would be in-consistent.
"In-"consistent, heh :-) >>> a = iter("abcde") >>> a.__contains__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'str_iterator' object has no attribute '__contains__' >>> 'b' in a True >>> list(a) ['c', 'd', 'e'] https://docs.python.org/3/reference/expressions.html#membership-test-operati... The "in" operator is built on iteration, but can be overridden by the `__contains__` method.
What you're asking for can best be spelled with any/all and iteration, not a new operator.
I completely agree. -- Steve

On Thu, Oct 28, 2021 at 05:25:52PM +1100, Chris Angelico wrote:
But the "in" operator isn't built on iteration, so that would be in-consistent.
"In-"consistent, heh :-)
\>\>\> a = iter("abcde") \>>> a.__contains__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'str_iterator' object has no attribute '__contains__' \>>> 'b' in a True \>>> list(a) ['c', 'd', 'e']
https://docs.python.org/3/reference/expressions.html#membership-test-operati...
The "in" operator is built on iteration, but can be overridden by the `__contains__` method.
What you're asking for can best be spelled with any/all and iteration, not a new operator.
I completely agree. I was replying to this.

On Thu, Oct 28, 2021 at 9:05 PM Steven D'Aprano <steve@pearwood.info> wrote:
On Thu, Oct 28, 2021 at 05:25:52PM +1100, Chris Angelico wrote:
But the "in" operator isn't built on iteration, so that would be in-consistent.
"In-"consistent, heh :-)
Couldn't resist.
>>> a = iter("abcde") >>> a.__contains__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'str_iterator' object has no attribute '__contains__' >>> 'b' in a True >>> list(a) ['c', 'd', 'e']
https://docs.python.org/3/reference/expressions.html#membership-test-operati...
The "in" operator is built on iteration, but can be overridden by the `__contains__` method.
Ah, my bad. Didn't realise that it has a default implementation like that. But still, the fundamental here is that it can and often will be overridden. ChrisA

On Thu, Oct 28, 2021 at 3:04 AM Steven D'Aprano
The "in" operator is built on iteration, but can be overridden by the `__contains__` method.
I would say it is built on __contains__, but will fall back on iteration :-) Effectively the same, but conceptually a bit different. There is also the expectation that the two methods provide the same results, as with dicts. -CHB -- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
participants (5)
-
Alexandre Brault
-
Chris Angelico
-
Christopher Barker
-
Jeremiah Vivian
-
Steven D'Aprano