Как len (генератор ()) [дубликат]

этот вопрос уже есть ответ здесь:

генераторы Python очень полезны. Они имеют преимущества перед функциями, возвращающими списки. Тем не менее, вы могли бы len(list_returning_function()). Есть ли способ len(generator_function())?

обновление:
Конечно!--2--> будет работать.....
Я пытаюсь использовать генератор, который я создал внутри нового генератора, который я создаю. В рамках расчета в новом генераторе он должен знать длину старого. Однако я хотел бы сохранить оба из них вместе с теми же свойствами, что и генератор, в частности - не поддерживать весь список в памяти, как это может быть очень долго.

обновление 2:
Предположим, генератор знает ее длина, даже с первого шага. Кроме того, нет причин поддерживать len() синтаксис. Пример-если функции в Python являются объектами, не могу ли я назначить длину переменной этого объекта, которая будет доступна для нового генератора?

8 ответов


генераторы не имеют длины, они не являются коллекциями в конце концов.

генераторы функции с внутренним состоянием (и причудливый синтаксис). Вы можете неоднократно вызывать их, чтобы получить последовательность значений, поэтому вы можете использовать их в цикле. Но они не содержат никаких элементов, поэтому запрос длины генератора похож на запрос длины функции.

Если функции в 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)

преобразование list Это было предложено в других ответах, это лучший способ, если вы все еще хотите обработать элементы генератора после этого, но имеет один недостаток: он использует память O(n). Вы можете подсчитать элементы в генераторе, не используя столько памяти с:

sum(1 for x in generator)

конечно, имейте в виду, что это может быть медленнее, чем len(list(generator)) в общих реализациях Python, и если генераторы достаточно длинные, чтобы сложность памяти имела значение, операция будет это займет некоторое время. Тем не менее, я лично предпочитаю это решение, поскольку оно описывает то, что я хочу получить, и оно не дает мне ничего лишнего, что не требуется (например, Список всех элементов).

также прислушайтесь к совету дельнана: если вы отбрасываете выход генератора, очень вероятно, что есть способ вычислить количество элементов без его запуска или подсчета их другим способом.


Предположим, у нас есть генератор:

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)

можно использовать len(list(generator_function()). Однако это потребляет генератор, но это единственный способ узнать, сколько элементов генерируется. Таким образом, вы можете сохранить список где-нибудь, если вы также хотите использовать элементы.

a = list(generator_function())
print(len(a))
print(a[0])

вы можете len(list(generator)) но вы, вероятно, могли бы сделать что-то более эффективное, если вы действительно намерены отказаться от результатов.


можно использовать 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

вы можете объединить преимущества генераторов с уверенностью len(), создав свой собственный iterable объект:

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, но не создает явный список.


можно использовать 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 находится в глобальном пространстве имен, поэтому импорт не нужен.