Длина выхода генератора [дубликат]
Вопрос
На этот вопрос уже есть ответ здесь:
Python предоставляет хороший метод для получения длины нетерпеливой итерации: len(x)
то есть.Но я не смог найти ничего подобного для ленивых итераций, представленных генераторами и функциями.Конечно, нетрудно написать что-то вроде:
def iterlen(x):
n = 0
try:
while True:
next(x)
n += 1
except StopIteration: pass
return n
Но не могу избавиться от ощущения, что переделываю велосипед.
(Пока я печатал функцию, мне в голову пришла мысль:возможно, такой функции действительно нет, потому что она «уничтожает» свой аргумент.Хотя для моего случая это не проблема).
P.S.:по поводу первых ответов - да, что-то вроде len(list(x))
тоже будет работать, но это резко увеличивает использование памяти.
П.П.С.:перепроверил...P.S. Не обращайте внимания, кажется, я допустил ошибку при попытке, все работает нормально.Извините за беспокойство.
Решение
Его нет, потому что в общем случае вы не можете этого сделать — что, если у вас есть ленивый бесконечный генератор?Например:
def fib():
a, b = 0, 1
while True:
a, b = b, a + b
yield a
Это никогда не заканчивается, но будет генерировать числа Фибоначчи.Вы можете получить столько чисел Фибоначчи, сколько захотите, позвонив next()
.
Если вам действительно нужно знать количество имеющихся элементов, вы все равно не сможете линейно перебирать их один раз, поэтому просто используйте другую структуру данных, например обычный список.
Другие советы
Самый простой способ, вероятно, просто sum(1 for _ in gen)
где gen — ваш генератор.
def count(iter):
return sum(1 for _ in iter)
Или еще лучше:
def count(iter):
try:
return len(iter)
except TypeError:
return sum(1 for _ in iter)
Если он не повторяется, он выдаст TypeError
.
Или, если вы хотите посчитать в генераторе что-то конкретное:
def count(iter, key=None):
if key:
if callable(key):
return sum(bool(key(x)) for x in iter)
return sum(x == key for x in iter)
try:
return len(iter)
except TypeError:
return sum(1 for _ in iter)
Итак, для тех, кто хотел бы узнать краткое содержание этого обсуждения.Окончательные высшие баллы за подсчет генераторного выражения длиной 50 миллионов с использованием:
len(list(gen))
,len([_ for _ in gen])
,sum(1 for _ in gen),
ilen(gen)
(от more_itertool),reduce(lambda c, i: c + 1, gen, 0)
,
отсортированные по производительности выполнения (включая потребление памяти), вас удивят:
```
1:test_list.py:8:0,492 КиБ
gen = (i for i in data*1000); t0 = monotonic(); len(list(gen))
('список, сек', 1.9684218849870376)
2:test_list_compr.py:8:0,867 КиБ
gen = (i for i in data*1000); t0 = monotonic(); len([i for i in gen])
('list_compr, сек', 2.5885991149989422)
3:test_sum.py:8:0,859 КиБ
gen = (i for i in data*1000); t0 = monotonic(); sum(1 for i in gen); t1 = monotonic()
('сумма, сек', 3.441088170016883)
4:more_itertools/more.py:413:1,266 КиБ
d = deque(enumerate(iterable, 1), maxlen=1)
test_ilen.py:10: 0.875 KiB
gen = (i for i in data*1000); t0 = monotonic(); ilen(gen)
('илен, сек', 9.812256851990242)
5:test_reduce.py:8:0,859 КиБ
gen = (i for i in data*1000); t0 = monotonic(); reduce(lambda counter, i: counter + 1, gen, 0)
('Cream, Sec', 13,436614598002052) `` `` `` `` `` ``
Так, len(list(gen))
является наиболее частым и менее потребляющим память
Вы можете использовать enumerate() для перебора сгенерированного потока данных, а затем вернуть последнее число — количество элементов.
Я пытался использовать itertools.count() с itertools.izip(), но безуспешно.Это лучший/самый короткий ответ, который я придумал:
#!/usr/bin/python
import itertools
def func():
for i in 'yummy beer':
yield i
def icount(ifunc):
size = -1 # for the case of an empty iterator
for size, _ in enumerate(ifunc()):
pass
return size + 1
print list(func())
print 'icount', icount(func)
# ['y', 'u', 'm', 'm', 'y', ' ', 'b', 'e', 'e', 'r']
# icount 10
Решение Камиля Кисиэля намного лучше:
def count_iterable(i):
return sum(1 for e in i)
Использовать уменьшить (функция, итерируемый [, инициализатор]) для эффективного использования памяти чисто функционального решения:
>>> iter = "This string has 30 characters."
>>> reduce(lambda acc, e: acc + 1, iter, 0)
30
По определению, только подмножество этих генераторов вернет результат после определенного количества аргументов (имеет заранее определенную длину), и даже в этом случае только подмножество этих конечных генераторов имеет предсказуемый конец (доступ к генератору может иметь побочные эффекты, которые мог остановить генератор раньше).
Если вы хотите реализовать методы длины для своего генератора, вам нужно сначала определить, что вы считаете «длиной» (это общее количество элементов?количество оставшихся элементов?), а затем оберните генератор в класс.Вот пример:
class MyFib(object):
"""
A class iterator that iterates through values of the
Fibonacci sequence, until, optionally, a maximum length is reached.
"""
def __init__(self, length):
self._length = length
self._i = 0
def __iter__(self):
a, b = 0, 1
while not self._length or self._i < self._length:
a, b = b, a + b
self._i += 1
yield a
def __len__(self):
"This method returns the total number of elements"
if self._length:
return self._length
else:
raise NotImplementedError("Infinite sequence has no length")
# or simply return None / 0 depending
# on implementation
Вот как его использовать:
In [151]: mf = MyFib(20)
In [152]: len(mf)
Out[152]: 20
In [153]: l = [n for n in mf]
In [154]: len(l)
Out[154]: 20
In [155]: l
Out[155]:
[1,
1,
2,
...
6765]
In [156]: mf0 = MyFib(0)
In [157]: len(mf0)
---------------------------------------------------------------------------
NotImplementedError Traceback (most recent call last)
<ipython-input-157-2e89b32ad3e4> in <module>()
----> 1 len(mf0)
/tmp/ipython_edit_TWcV1I.py in __len__(self)
22 return self._length
23 else:
---> 24 raise NotImplementedError
25 # or simply return None / 0 depending
26 # on implementation
NotImplementedError:
In [158]: g = iter(mf0)
In [159]: l0 = [g.next(), g.next(), g.next()]
In [160]: l0
Out[160]: [1, 1, 2]
Попробуйте more_itertools
пакет для простого решения.Пример:
>>> import more_itertools
>>> it = iter("abcde") # sample generator
>>> it
<str_iterator at 0x4ab3630>
>>> more_itertools.ilen(it)
5
Видеть эта почта еще один прикладной пример.
Это хак, но если вы действительно хотите иметь len
работая над общей итерацией (попутно потребляя ее), вы можете создать свою собственную версию len
.
А len
функция по существу эквивалентна следующей (хотя реализации обычно предусматривают некоторую оптимизацию, чтобы избежать дополнительного поиска):
def len(iterable):
return iterable.__len__()
Поэтому мы можем определить нашу new_len
попробовать это, и если __len__
не существует, посчитайте количество элементов самостоятельно, потребляя итерацию:
def new_len(iterable):
try:
return iterable.__len__()
except AttributeError:
return sum(1 for _ in iterable)
Вышеупомянутое работает в Python 2/3 и (насколько мне известно) должно охватывать все мыслимые виды итераций.