web-dev-qa-db-ja.com

len(generator())する方法

Pythonジェネレーター は非常に便利です。リストを返す関数よりも利点があります。ただし、len(list_returning_function())はできます。 len(generator_function())する方法はありますか?

UPDATE:
もちろんlen(list(generator_function()))は機能します。...
私が作成している新しいジェネレーター内で作成したジェネレーターを使用しようとしています。新しいジェネレーターの計算の一部として、古いジェネレーターの長さを知る必要があります。しかし、私はそれらの両方をジェネレータと同じプロパティで一緒に保持したいと思います、具体的には、メモリ内のリスト全体を維持しないでくださいvery長いです。

更新2:
ジェネレーターは、最初のステップからでもターゲットの長さを知っていると仮定します。また、len()構文を維持する理由はありません。例-Pythonの関数がオブジェクトである場合、新しいジェネレーターがアクセスできるこのオブジェクトの変数に長さを割り当てることはできませんか?

124
Jonathan

ジェネレータには長さがなく、結局コレクションではありません。

ジェネレータは内部状態を持つ関数(および派手な構文)です。繰り返し呼び出して値のシーケンスを取得できるため、ループで使用できます。ただし、要素は含まれていないため、ジェネレーターの長さを求めることは、関数の長さを求めることに似ています。

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)
58
Jochen Ritzel

他の回答で提案されているlistへの変換は、後でジェネレーター要素を処理したい場合に最適な方法ですが、欠点が1つあります:O(n)メモリーを使用します。以下を使用して、それほど多くのメモリを使用せずにジェネレーター内の要素をカウントできます。

sum(1 for x in generator)

もちろん、これは一般的なPython実装のlen(list(generator))よりも遅くなる可能性があることに注意してください。ジェネレーターがメモリの複雑さを考慮して十分に長い場合、操作にはかなり時間がかかります。それでも、私は取得したいものを説明するため、このソリューションを個人的に好みます。また、不要なもの(すべての要素のリストなど)は提供しません。

また、delnanのアドバイスにも耳を傾けてください。ジェネレーターの出力を破棄する場合、エレメントを実行せずに、または別の方法でカウントすることにより、エレメントの数を計算する方法がありそうです。

208
Rosh Oxymoron

ジェネレーターがあるとします:

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)
19
unutbu

len(list(generator_function())を使用できます。ただし、これはジェネレーターを消費しますが、生成される要素の数を確認できる唯一の方法です。そのため、アイテムを使用する場合は、リストをどこかに保存することもできます。

a = list(generator_function())
print(len(a))
print(a[0])
13
Greg Hewgill

len(list(generator))はできますが、結果を本当に破棄したい場合は、おそらくもっと効率的にすることができます。

7
Ben Jackson

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
6
cyborg

独自の反復可能なオブジェクトを作成することにより、ジェネレーターの利点と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を取得できるオブジェクトを返しますが、明示的なリストは作成しません。

4
Ned Batchelder

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はグローバル名前空間にあるため、インポートは不要です。

4
hwiechers