Recently I've been wondering why __contains__ casts all of it's returns to be boolean values. Specifically I'd like to propose that __contains__'s return values be passed directly back as the result of the `in` operation. As a result I'd further propose the introduction of __not_contains__, which is the `not in` operator. The primary usecase for this is something like an expression recorder. For example in SQLAlchemy one can do:
User.id == 3, but not User.id in SQLList([1, 2, 3]), because it returns a bool always. __not_contains__ is needed to be the analog of this, as it cannot be merely be a negation of __contains__ when it's returning a non-bool result.
There should be no backwards compatibility issues to making __contains__ return non-bools, unless there is code like:
x = y in foo assert type(x) is bool
However, for __not_contains__ I'd propose the default implementation be:
def __not_contains__(self, val): x = val in self if type(x) is not bool: raise TypeError("%s returned a non-boolean value from __contains__ and didn't provide an implementation of __not_contains__") return not x
This is not perfect (and it's at odds with the fact that __ne__ doesn't return not self == other), but it seems to allow both the desired flexibility and backwards compatibility.
I'm not sure if this is something that'd be covered by the language moratorium, but if not I can try putting together a patch for this.