Python __getitem__ и оператор приводят к странному поведению

34

Чем объясняется следующее поведение:

class Foo:
    def __getitem__(self, item):
        print("?")
        return 1

f = Foo()

1 in f  # prints one ? and returns True

5 in f  # prints ? forever until you raise a Keyboard Exception

# Edit: eventually this fails with OverflowError: iter index too large
Мэтью Мойзен
источник

Ответы:

45

Если объект не имеет __contains__реализации, inвозвращается значение по умолчанию, которое в основном работает так:

def default__contains__(self, element):
    for thing in self:
        if thing == element:
            return True
    return False

И если объект не имеет __iter__реализации, forвозвращается значение по умолчанию, которое в основном работает так:

def default__iter__(self):
    i = 0
    try:
        while True:
            yield self[i]
            i += 1
    except IndexError:
        pass

Эти значения по умолчанию используются, даже если объект не предназначен для последовательности.

Ваш 1 in fи 5 in fтесты используют запасные варианты по умолчанию для inи for, что приводит к наблюдаемому поведению. 1 in fнаходит 1сразу, но ваш __getitem__никогда не возвращается 5, поэтому 5 in fработает вечно.

(Ну, на самом деле, в эталонной реализации Python __iter__резервный вариант по умолчанию хранит индекс в переменной типа C-типа Py_ssize_t, поэтому, если вы будете ждать достаточно долго, эта переменная будет исчерпана, а Python вызовет OverflowError . Если вы это увидели, вы должен быть в 32-битной сборке Python. Компьютеров не было достаточно долго, чтобы кто-нибудь смог запустить его на 64-битной Python.)

user2357112 поддерживает Monica
источник
Что касается OverflowError, я запустил это как на 64, так и на 32 битах, и вы правы, я видел это только на 32 битах.
Мэтью Мойзен
Вы случайно не знаете документацию, которая это объясняет? Я хотел бы прочитать о том, почему было принято решение об этой реализации.
Мэтью Мойзен
3
@Matthew Expressions> Тестовые операции членства , также объект .__ содержит__ и параграф прямо над ним
wjandrea
4
@MatthewMoisen: эти значения по умолчанию были исходным поведением forи in, предшествовавшим введению __iter__и __contains__. Смотрите документацию по Python 1.4 здесь и здесь .
user2357112 поддерживает Monica