Search code examples
pythonlistpython-3.xcontainment

How does the behavior of `in` differ from `index` in python lists


Much to my own frustration, I managed to make a list of objects in python such that the following fails:

if foo in lst:
    lst.index(foo) # ValueError: <foo> is not in list

I assure you, no trickery here:

  1. foo is an object with a custom __hash__ and __eq__, and is not being modified elsewhere
  2. both functions are idempotent and do not modify state
  3. lst is a standard python [] with no curses

Solution

  • The only difference between obj in listobject (or listobject.__contains__(obj)) and listobject.index() is that comparison is inverted.

    list_contains does:

    for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i)
        cmp = PyObject_RichCompareBool(el, PyList_GET_ITEM(a, i),
                                           Py_EQ);
    

    while listindex does;

    for (i = start; i < stop && i < Py_SIZE(self); i++) {
        int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ);
    

    where el and v are the looked-for object, and PyList_GET_ITEM(a, i) and self->ob_item[i] are the objects contained in the list.

    So lst.index() throws a ValueError exception because all of the other.__eq__(foo) calls return False and foo.__eq__(other) is never consulted. foo in lst works because foo.__eq__(other) does return True for at least one value.

    You haven't given us a sample list and __eq__ implementation to verify why __eq__ returns False for all other.__eq__(foo) calls.