This is something that has been awkward for sympy which has symbolic sets:
In [7]: from sympy import *
In [8]: x = symbols('x')
In [9]: Contains(2, Integers)
Out[9]: True
In [10]: Contains(x, Integers)
Out[10]: x ∈ ℤ
In [11]: 2 in Integers
Out[11]: True
In [12]: x in Integers
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[12], line 1
----> 1 x in Integers
File ~/current/sympy/sympy.git/sympy/sets/sets.py:795, in Set.__contains__(self, other)
791 b = tfn[c]
792 if b is None:
793 # x in y must evaluate to T or F; to entertain a None
794 # result with Set use y.contains(x)
--> 795 raise TypeError('did not evaluate to a bool: %r' % c)
796 return b
TypeError: did not evaluate to a bool: None
It isn’t possible to overload __contains__
to return a non-bool because it will always be coerced to a bool:
class Set:
def __init__(self, name):
self.name = name
def __repr__(self):
return self.name
def __lt__(self, other):
return StrictSubset(self, other)
def __contains__(self, element):
return Contains(self, element)
class StrictSubset:
def __init__(self, lhs, rhs):
self.lhs = lhs
self.rhs = rhs
def __repr__(self):
return f'{self.lhs} ⊂ {self.rhs}'
class Contains:
def __init__(self, element, set_):
self.element = element
self.set_ = set_
def __repr__(self):
return f'{self.element} ∈ {self.set_}'
Then we have:
In [24]: A = Set('A')
In [25]: B = Set('B')
In [26]: A < B
Out[26]: A ⊂ B
In [27]: Contains(A, B)
Out[27]: A ∈ B
In [28]: A in B
Out[28]: True
In [29]: A.__contains__(B)
Out[29]: A ∈ B
In [30]: bool(A.__contains__(B))
Out[30]: True
Returning True here is not correct so sympy chooses to raise an error if the __contains__
would otherwise return anything other than True or False.
It would be better for this usage if it were possible to return non-bool from __contains__
and have Python respect that rather than coercing to bool
in the same way that it does for other operators like <
, &
, etc. My guess is that the reason __contains__
is different is related to the syntactic constructs like in
and not in
or something but I’m not sure. It is also possible that this case was just overlooked because it wasn’t needed by the likes of numpy for array operations.
Changing this in Python so that the interpreter does not coerce would not be backwards compatible because although probably the vast majority of __contains__
methods will already return bool
there will almost certainly be some code somewhere that currently relies on the interpreter to do this coercion. I think that the change would be worth it anyway though and I have been meaning for some time to propose it. I haven’t gotten round to proposing this because I haven’t found the time myself to prepare a patch and I thought it would be better to do that first rather than propose an idea that I don’t yet have time to implement.