blue note
blue note

Reputation: 29081

Is it possible for `__contains__` to return non-boolean value?

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

Answers (4)

timgeb
timgeb

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

zvone
zvone

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 returns True if y.__contains__(x) returns a true value, and False 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

chepner
chepner

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

user2722968
user2722968

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

Related Questions