Как len (генератор())
Генераторы Python очень полезны. Они имеют преимущества перед функциями, которые возвращают списки. Однако вы могли бы len(list_returning_function())
. Есть ли способ len(generator_function())
?
UPDATE:
Конечно, len(list(generator_function()))
будет работать.....
Я пытаюсь использовать генератор, который я создал внутри нового генератора, который я создаю. В рамках расчета в новом генераторе необходимо знать длину старого. Однако я хотел бы сохранить оба из них вместе с теми же свойствами, что и генератор, в частности - не поддерживать весь список в памяти, поскольку он может быть очень длинным.
ОБНОВЛЕНИЕ 2:
Предположим, что генератор знает длину цели даже с первого шага. Кроме того, нет причин поддерживать синтаксис len()
. Пример. Если функции в Python являются объектами, не могу ли я назначить длину переменной этого объекта, которая будет доступна для нового генератора?
Ответы
Ответ 1
Генераторы не имеют длины, они не являются коллекциями в конце концов.
Генераторы - это функции с внутренним состоянием (и причудливым синтаксисом). Вы можете повторно вызвать их, чтобы получить последовательность значений, чтобы вы могли использовать их в цикле. Но они не содержат никаких элементов, поэтому запрос длины генератора похож на запрос длины функции.
если функции в Python являются объектами, я не могу назначить длину переменная этого объекта, которая будет доступна для нового генератора?
Функции - это объекты, но вы не можете назначать им новые атрибуты. Вероятно, причина заключается в том, чтобы такой базовый объект был максимально эффективным.
Однако вы можете просто вернуть пары (generator, length)
из своих функций или обернуть генератор простым простейшим способом:
class GeneratorLen(object):
def __init__(self, gen, length):
self.gen = gen
self.length = length
def __len__(self):
return self.length
def __iter__(self):
return self.gen
g = some_generator()
h = GeneratorLen(g, 1)
print len(h), list(h)
Ответ 2
Преобразование в list
, которое было предложено в других ответах, является лучшим способом, если вы все еще хотите обработать элементы генератора впоследствии, но имеет один недостаток: он использует память O (n). Вы можете подсчитать элементы в генераторе, не используя столько памяти:
sum(1 for x in generator)
Конечно, имейте в виду, что это может быть медленнее, чем len(list(generator))
в общих реализациях Python, и если генераторы достаточно длинны для сложности памяти, операция займет довольно много времени. Тем не менее, я лично предпочитаю это решение, поскольку оно описывает то, что я хочу получить, и оно не дает мне ничего лишнего, что не требуется (например, список всех элементов).
Также слушайте советы delnan: если вы отбрасываете вывод генератора, очень вероятно, что есть способ вычислить количество элементов без его запуска или путем подсчета их другим способом.
Ответ 3
Предположим, что мы имеем генератор:
def gen():
for i in range(10):
yield i
Мы можем обернуть генератор вместе с известной длиной в объекте:
import itertools
class LenGen(object):
def __init__(self,gen,length):
self.gen=gen
self.length=length
def __call__(self):
return itertools.islice(self.gen(),self.length)
def __len__(self):
return self.length
lgen=LenGen(gen,10)
Экземпляры LenGen
являются самими генераторами, так как их вызов возвращает итератор.
Теперь мы можем использовать генератор lgen
вместо gen
и также получить доступ к len(lgen)
:
def new_gen():
for i in lgen():
yield float(i)/len(lgen)
for i in new_gen():
print(i)
Ответ 4
Вы можете использовать len(list(generator_function())
. Однако это потребляет генератор, но это единственный способ узнать, сколько элементов создано. Поэтому вы можете сохранить список где-нибудь, если вы также хотите использовать элементы.
a = list(generator_function())
print(len(a))
print(a[0])
Ответ 5
Вы можете len(list(generator))
, но вы, вероятно, могли бы сделать что-то более эффективное, если вы действительно собираетесь отказаться от результатов.
Ответ 6
Вы можете комбинировать преимущества генераторов с определенностью len()
, создав свой собственный итерируемый объект:
class MyIterable(object):
def __init__(self, n):
self.n = n
def __len__(self):
return self.n
def __iter__(self):
self._gen = self._generator()
return self
def _generator(self):
# Put your generator code here
i = 0
while i < self.n:
yield i
i += 1
def next(self):
return next(self._gen)
mi = MyIterable(100)
print len(mi)
for i in mi:
print i,
Это в основном простая реализация xrange
, которая возвращает объект, который вы можете взять len, но не создает явный список.
Ответ 7
Вы можете использовать reduce
.
Для Python 3:
>>> import functools
>>> def gen():
... yield 1
... yield 2
... yield 3
...
>>> functools.reduce(lambda x,y: x + 1, gen(), 0)
В Python 2 reduce
находится в глобальном пространстве имен, поэтому импорт не нужен.
Ответ 8
Вы можете использовать send
как взломать:
def counter():
length = 10
i = 0
while i < length:
val = (yield i)
if val == 'length':
yield length
i += 1
it = counter()
print(it.next())
#0
print(it.next())
#1
print(it.send('length'))
#10
print(it.next())
#2
print(it.next())
#3