Как 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