У меня есть класс с методами __iter__ и __len__. Последний использует первое для подсчета всех элементов.
Он работает следующим образом:
class A:
def __iter__(self):
print("iter")
for _ in range(5):
yield "something"
def __len__(self):
print("len")
n = 0
for _ in self:
n += 1
return n
Теперь, если мы возьмем, например, длина экземпляра он печатает len и iter, как ожидалось:
>>> len(A())
len
iter
5
Но если мы назовем list(), он вызывает как __iter__, так и __len__:
>>> list(A())
len
iter
iter
['something', 'something', 'something', 'something', 'something']
Он работает как ожидалось, если мы создадим выражение генератора:
>>> list(x for x in A())
iter
['something', 'something', 'something', 'something', 'something']
Я бы предположил, что list(A()) и list(x for x in A()) работают одинаково, но они не работают.
Обратите внимание, что он сначала вызывает __iter__, затем __len__, затем перебирает итератор:
class B:
def __iter__(self):
print("iter")
def gen():
print("gen")
yield "something"
return gen()
def __len__(self):
print("len")
return 1
print(list(B()))
Вывод:
iter
len
gen
['something']
Как я могу получить list() не для вызова __len__, чтобы итераторы экземпляров экземпляров не потреблялись дважды? Я мог бы определить, например. a length или size, и затем вызывается A().size(), но это меньше, чем pythonic.
Я попытался вычислить длину в __iter__ и кешировать ее так, чтобы последующие вызовы __len__ не нуждались в повторении, но list() вызывали __len__ без начала итерации, поэтому он не работает.
Обратите внимание, что в моем случае я работаю с очень большими коллекциями данных, поэтому кеширование всех элементов не является вариантом.