У меня есть класс с методами __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__
без начала итерации, поэтому он не работает.
Обратите внимание, что в моем случае я работаю с очень большими коллекциями данных, поэтому кеширование всех элементов не является вариантом.