Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is "... object is not iterable" A Misleading Error For "x in y"?

Consider the following two classes:

class Foo0(object):                                                 
    pass

class Foo1(object):
    def __contains__(self, _):
        return False

Neither of them is iterable, as can be observed by trying for i in Foo1(): pass:

Traceback (most recent call last):
   File "stuff.py", line 11, in <module>
    for i in Foo1(): pass
TypeError: 'Foo1' object is not iterable

Conversely, 3 in Foo1() is valid (as Foo1 has __contains__), whereas 3 in Foo0() is not:

Traceback (most recent call last):
  File "stuff.py", line 9, in <module>
    3 in Foo0()
TypeError: argument of type 'Foo0' is not iterable

I find the error misleading. The point is not that Foo0 is not iterable - after all Foo1 is not iterable too, so that's not the inherent reason. Moreover, as @niemmi and @tobias_k correctly note below, if a class does not support __contains__, __iter__ is used as a fallback, which is the direct cause for this error. However this makes things worse, as a user might try to solve this error by implementing __iter__, which is a very inefficient means of implementing __contains__.

Why was this chosen as the error, then?

like image 232
Ami Tavory Avatar asked Mar 13 '23 22:03

Ami Tavory


1 Answers

If the object doesn't have __contains__ method Python automatically tries to iterate over it to see if it can find the element. Since Foo0 isn't an iterable you will see the error.

UPDATE: Python language reference explains this in bit more detail:

For user-defined classes which define the contains() method, x in y is true if and only if y.contains(x) is true.

For user-defined classes which do not define contains() but do define iter(), x in y is true if some value z with x == z is produced while iterating over y. If an exception is raised during the iteration, it is as if in raised that exception.

Lastly, the old-style iteration protocol is tried: if a class defines getitem(), x in y is true if and only if there is a non-negative integer index i such that x == y[i], and all lower integer indices do not raise IndexError exception. (If any other exception is raised, it is as if in raised that exception).

like image 198
niemmi Avatar answered Mar 30 '23 00:03

niemmi