Pythonジェネレーター は非常に便利です。リストを返す関数よりも利点があります。ただし、len(list_returning_function())
はできます。 len(generator_function())
する方法はありますか?
UPDATE:
もちろんlen(list(generator_function()))
は機能します。...
私が作成している新しいジェネレーター内で作成したジェネレーターを使用しようとしています。新しいジェネレーターの計算の一部として、古いジェネレーターの長さを知る必要があります。しかし、私はそれらの両方をジェネレータと同じプロパティで一緒に保持したいと思います、具体的には、メモリ内のリスト全体を維持しないでくださいvery長いです。
更新2:
ジェネレーターは、最初のステップからでもターゲットの長さを知っていると仮定します。また、len()
構文を維持する理由はありません。例-Pythonの関数がオブジェクトである場合、新しいジェネレーターがアクセスできるこのオブジェクトの変数に長さを割り当てることはできませんか?
ジェネレータには長さがなく、結局コレクションではありません。
ジェネレータは内部状態を持つ関数(および派手な構文)です。繰り返し呼び出して値のシーケンスを取得できるため、ループで使用できます。ただし、要素は含まれていないため、ジェネレーターの長さを求めることは、関数の長さを求めることに似ています。
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
への変換は、後でジェネレーター要素を処理したい場合に最適な方法ですが、欠点が1つあります:O(n)メモリーを使用します。以下を使用して、それほど多くのメモリを使用せずにジェネレーター内の要素をカウントできます。
sum(1 for x in generator)
もちろん、これは一般的なPython実装のlen(list(generator))
よりも遅くなる可能性があることに注意してください。ジェネレーターがメモリの複雑さを考慮して十分に長い場合、操作にはかなり時間がかかります。それでも、私は取得したいものを説明するため、このソリューションを個人的に好みます。また、不要なもの(すべての要素のリストなど)は提供しません。
また、delnanのアドバイスにも耳を傾けてください。ジェネレーターの出力を破棄する場合、エレメントを実行せずに、または別の方法でカウントすることにより、エレメントの数を計算する方法がありそうです。
ジェネレーターがあるとします:
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()
の確実性を組み合わせることができます。
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
はグローバル名前空間にあるため、インポートは不要です。