ジェネレーターに , , のようなアイテムがないかどうかをテストする簡単な方法はありpeek
ますhasNext
かisEmpty
?
23 に答える
提案:
def peek(iterable):
try:
first = next(iterable)
except StopIteration:
return None
return first, itertools.chain([first], iterable)
使用法:
res = peek(mysequence)
if res is None:
# sequence is empty. Do stuff.
else:
first, mysequence = res
# Do something with first, maybe?
# Then iterate over the sequence:
for element in mysequence:
# etc.
あなたの質問に対する簡単な答えは: いいえ、簡単な方法はありません。回避策はたくさんあります。
ジェネレーターが何であるかを考えると、単純な方法があるべきではありません:シーケンスをメモリに保持せずに値のシーケンスを出力する方法です。したがって、後方トラバーサルはありません。
has_next 関数を記述したり、必要に応じて派手なデコレータを使用したメソッドとしてジェネレータに平手打ちしたりすることもできます。
next(generator, None) is not None
または、ジェネレーターにないNone
ことがわかっている値を置き換えます。
編集:はい、これはジェネレーターで1つのアイテムをスキップします。ただし、多くの場合、ジェネレーターが空であるかどうかを検証目的でのみチェックし、実際には使用しません。または、そうでなければ私は次のようなことをします:
def foo(self):
if next(self.my_generator(), None) is None:
raise Exception("Not initiated")
for x in self.my_generator():
...
つまり、これは、ジェネレーターが のように関数から派生している場合に機能しgenerator()
ます。
最良のアプローチは、私見ですが、特別なテストを避けることです。ほとんどの場合、ジェネレーターの使用はテストです。
thing_generated = False
# Nothing is lost here. if nothing is generated,
# the for block is not executed. Often, that's the only check
# you need to do. This can be done in the course of doing
# the work you wanted to do anyway on the generated output.
for thing in my_generator():
thing_generated = True
do_work(thing)
それでも十分でない場合でも、明示的なテストを実行できます。この時点でthing
、最後に生成された値が含まれます。何も生成されなかった場合は、変数を定義していない限り、未定義になります。の値を確認できますがthing
、それは少し信頼できません。代わりに、ブロック内にフラグを設定し、後で確認してください。
if not thing_generated:
print "Avast, ye scurvy dog!"
私は2番目の解決策、特に私自身は使用しない解決策を提供するのは嫌いですが、他の回答のように、これを絶対に実行し、ジェネレーターを消費しないようにする必要がある場合:
def do_something_with_item(item):
print item
empty_marker = object()
try:
first_item = my_generator.next()
except StopIteration:
print 'The generator was empty'
first_item = empty_marker
if first_item is not empty_marker:
do_something_with_item(first_item)
for item in my_generator:
do_something_with_item(item)
これはジェネレーターの使用方法ではないと信じているため、このソリューションは本当に好きではありません。
ジェネレーターが空かどうかを確認するために必要なことは、次の結果を取得しようとすることだけです。もちろん、その結果を使用する準備ができていない場合は、後で再度返すために保存する必要があります。
テストを追加するために既存のイテレータに追加できるラッパー クラスを次に示します。__nonzero__
これにより、単純なif
. おそらく、デコレータにすることもできます。
class GenWrapper:
def __init__(self, iter):
self.source = iter
self.stored = False
def __iter__(self):
return self
def __nonzero__(self):
if self.stored:
return True
try:
self.value = next(self.source)
self.stored = True
except StopIteration:
return False
return True
def __next__(self): # use "next" (without underscores) for Python 2.x
if self.stored:
self.stored = False
return self.value
return next(self.source)
使用方法は次のとおりです。
with open(filename, 'r') as f:
f = GenWrapper(f)
if f:
print 'Not empty'
else:
print 'Empty'
繰り返しの開始時だけでなく、いつでも空であることを確認できることに注意してください。
>>> gen = (i for i in [])
>>> next(gen)
Traceback (most recent call last):
File "<pyshell#43>", line 1, in <module>
next(gen)
StopIteration
ジェネレーターの最後に発生StopIteration
します。あなたの場合、すぐに終了に達するため、例外が発生します。しかし、通常、次の値の存在をチェックするべきではありません。
あなたができる別のことは次のとおりです。
>>> gen = (i for i in [])
>>> if not list(gen):
print('empty generator')
明らかなアプローチで申し訳ありませんが、最善の方法は次のとおりです。
for item in my_generator:
print item
使用中にジェネレーターが空であることを検出しました。もちろん、ジェネレーターが空の場合、アイテムは表示されません。
これはあなたのコードに正確に適合しないかもしれませんが、これがジェネレーターのイディオムの目的です: 反復するため、アプローチを少し変更するか、ジェネレーターをまったく使用しない場合があります。
ジェネレーターを使用する前に知る必要がある場合は、いいえ、簡単な方法はありません。ジェネレーターを使用した後まで待つことができる場合は、簡単な方法があります。
was_empty = True
for some_item in some_generator:
was_empty = False
do_something_with(some_item)
if was_empty:
handle_already_empty_generator_case()
これはジェネレーターをラップする単純なデコレーターで、空の場合は None を返します。これは、ループする前にジェネレーターが何かを生成するかどうかをコードが知る必要がある場合に役立ちます。
def generator_or_none(func):
"""Wrap a generator function, returning None if it's empty. """
def inner(*args, **kwargs):
# peek at the first item; return None if it doesn't exist
try:
next(func(*args, **kwargs))
except StopIteration:
return None
# return original generator otherwise first item will be missing
return func(*args, **kwargs)
return inner
使用法:
import random
@generator_or_none
def random_length_generator():
for i in range(random.randint(0, 10)):
yield i
gen = random_length_generator()
if gen is None:
print('Generator is empty')
これが役立つ 1 つの例は、コードのテンプレート化 (例: jinja2) です。
{% if content_generator %}
<section>
<h4>Section title</h4>
{% for item in content_generator %}
{{ item }}
{% endfor %
</section>
{% endif %}
sum関数を使って解決しました。glob.iglob (ジェネレーターを返す) で使用した例については、以下を参照してください。
def isEmpty():
files = glob.iglob(search)
if sum(1 for _ in files):
return True
return False
*これはおそらく巨大なジェネレーターでは機能しませんが、小さなリストではうまく機能するはずです