
I DO expect this thread to be bombarded with negative replies. Currently, there are `in`/`not in` operators which work like this in Python:

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

On Mon, Oct 25, 2021 at 8:35 PM Steven D'Aprano <steve@pearwood.info> 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. 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

On Mon, Oct 25, 2021 at 9:33 PM Jeremiah Vivian <nohackingofkrowten@gmail.com> wrote:
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 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

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

On Mon, Oct 25, 2021 at 8:35 PM Steven D'Aprano <steve@pearwood.info> 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. 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

On Mon, Oct 25, 2021 at 9:33 PM Jeremiah Vivian <nohackingofkrowten@gmail.com> wrote:
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 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