Reputation: 29081
The documentation says that __contains__
should return true if item is in self, false otherwise. However, if the method returns a non-boolean value x
, it seems that python automatically converts it to bool(x)
.
Is there any way to avoid that, and return the actual value x
? Or is this feature behavior implemented directly in the interpreter and there's no way to change this?
Upvotes: 10
Views: 1170
Reputation: 78700
__bool__
is indeed being called on the return value of __contains__
.
Consider the following classes:
class BoolWithPrint:
def __init__(self, value):
self.value = value
def __bool__(self):
print("Im being booled.")
return self.value
class StrangeContains:
def __contains__(self, x):
return BoolWithPrint(x)
... which behave like this:
>>> True in StrangeContains()
Im being booled.
True
>>> False in StrangeContains()
Im being booled.
False
>>> 'stuff' in StrangeContains()
Im being booled.
[...]
TypeError: __bool__ should return bool, returned str
So as far as I know, you are out of luck. You could sneakily override __bool__
on the value __contains__
returns, but that will only delay the TypeError
because __bool__
must return True
or False
.
For additional context, see Can the Python bool() function raise an exception for an invalid argument?.
Upvotes: 5
Reputation: 19352
In Python documentation, section 6.10.2. Membership test operations says:
For user-defined classes which define the
__contains__()
method,x in y
returnsTrue
ify.__contains__(x)
returns a true value, andFalse
otherwise.
So clearly, if you return a non-bool, the in
operator will still return a boolean.
If you directly call __contains__
, then of course you will get whatever result is returned from it.
For example:
class X:
def __contains__(self, other):
return 11
x = X()
8 in x # True
x.__contains__(8) # 11
Upvotes: 5
Reputation: 531325
Note that it's not __contains__
that converts the value to a Boolean, but the in
operator that calls __contains__
. With
class Foo(list):
def __contains__(self, v):
if super().__contains__(v):
return v
else:
return False
>>> f = Foo([1,2,3])
>>> f.__contains__(2)
2
>>> 2 in f
True
Upvotes: 11
Reputation: 16485
A foo in bar
will be compiled to COMPARE_OP (in)
for CPython3. The implementation uses PySequence_Contain()
and then coerces to result to a bool
. So while you could return something else, you always end up with a bool after the call.
Upvotes: 6