ジェネレーターにアイテムがないか、peek、hasNext、isEmptyなどの行に沿って何かをテストする簡単な方法はありますか?
あなたの質問に対する簡単な答え:いいえ、簡単な方法はありません。多くの回避策があります。
ジェネレーターとは何かという理由で、単純な方法は実際にはないはずです。値のシーケンスを出力する方法メモリにシーケンスを保持せずに。したがって、後方への走査はありません。
Has_next関数を書くこともできれば、必要に応じて派手なデコレータを使用したメソッドとしてジェネレーターにスラップすることもできます。
提案:
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.
簡単な方法は、 next() のオプションのパラメーターを使用することです。これは、ジェネレーターが使い果たされた(または空の)場合に使用されます。例えば:
iterable = some_generator()
_exhausted = object()
if next(iterable, _exhausted) == _exhausted:
print('generator is empty')
編集:mehtunguhのコメントで指摘された問題を修正しました。
next(generator, None) is not None
または、None
を置き換えますが、既知の値はジェネレーターでnotです。
編集:はい、これはジェネレーターの1アイテムをスキップします。ただし、多くの場合、検証の目的でのみジェネレーターが空であるかどうかを確認し、実際には使用しません。または、次のようなことをします。
def foo(self):
if next(self.my_generator(), None) is None:
raise Exception("Not initiated")
for x in self.my_generator():
...
つまり、これは、generator()
のように、generatorがfunctionに由来する場合に機能します。
私見である最善のアプローチは、特別なテストを避けることです。ほとんどの場合、ジェネレーターの使用isテスト:
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番目の解決策、特に自分自身を使用しない解決策を提供することを嫌いますが、絶対にhadを使用してこれを行い、他の答えのようにジェネレータを消費しない場合:
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)
今、私はこのソリューションが本当に好きではありません。なぜなら、これはジェネレーターの使用方法ではないと考えているからです。
ジェネレータが空かどうかを確認するために必要なことは、次の結果を取得することだけです。もちろん、その結果を使用するためにreadyでない場合は、後でそれを返すために保存する必要があります。
__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'
反復の開始時だけでなく、いつでも空をチェックできることに注意してください。
明らかなアプローチで申し訳ありませんが、最善の方法はそうすることです。
for item in my_generator:
print item
これで、使用中にジェネレーターが空であることを検出できました。もちろん、ジェネレーターが空の場合、アイテムは表示されません。
これはコードに正確に適合しない可能性がありますが、これはジェネレーターのイディオムです:反復。
私はこの投稿がこの時点で5年前であることを認識していますが、これを行うための慣用的な方法を探しているときに発見し、私のソリューションが投稿されたことを知りませんでした。後世のために:
import itertools
def get_generator():
"""
Returns (bool, generator) where bool is true iff the generator is not empty.
"""
gen = (i for i in [0, 1, 2, 3, 4])
a, b = itertools.tee(gen)
try:
a.next()
except StopIteration:
return (False, b)
return (True, b)
もちろん、多くのコメンテーターが指摘していると思いますが、これはハッキングであり、特定の限られた状況(ジェネレーターに副作用がないなど)でのみ機能します。 YMMV。
私の場合、ジェネレーターのホストが関数に渡される前に生成されたかどうかを知る必要がありました。関数はアイテム、つまりZip(...)
をマージしました。解決策は、受け入れられた答えと似ていますが、十分に異なります:
定義:
def has_items(iterable):
try:
return True, itertools.chain([next(iterable)], iterable)
except StopIteration:
return False, []
使用法:
def filter_empty(iterables):
for iterable in iterables:
itr_has_items, iterable = has_items(iterable)
if itr_has_items:
yield iterable
def merge_iterables(iterables):
populated_iterables = filter_empty(iterables)
for items in Zip(*populated_iterables):
# Use items for each "slice"
私の特定の問題には、反復可能オブジェクトが空であるか、エントリの数がまったく同じであるという特性があります。
>>> 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')
ちょうどこのスレッドに落ちて、非常にシンプルで読みやすい答えが欠けていることに気付きました:
def is_empty(generator):
for item in generator:
return False
return True
アイテムを消費することを想定していない場合、最初のアイテムをジェネレータに再注入する必要があります。
def is_empty_no_side_effects(generator):
try:
item = next(generator)
def my_generator():
yield item
yield from generator
return my_generator(), False
except StopIteration:
return (_ for _ in []), True
例:
>>> g=(i for i in [])
>>> g,empty=is_empty_no_side_effects(g)
>>> empty
True
>>> g=(i for i in range(10))
>>> g,empty=is_empty_no_side_effects(g)
>>> empty
False
>>> list(g)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Cytoolzで peek 関数を使用します。
from cytoolz import peek
from typing import Tuple, Iterable
def is_empty_iterator(g: Iterable) -> Tuple[Iterable, bool]:
try:
_, g = peek(g)
return g, False
except StopIteration:
return g, True
この関数によって返される反復子は、引数として渡される元の反復子と同等になります。
以下はジェネレーターをラップするシンプルなデコレーターです。空の場合はNoneを返します。これは、ジェネレーターが何かを生成するかどうかをコードが知る必要がある場合に役立ちますbeforeループします。
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 %}
Any()の使用はどうですか?私は発電機でそれを使用し、それはうまく機能しています。 ここ これについて少し説明する人がいます
Mark Ransomの指示に従って、イテレータをラップして、先に覗き込んだり、値をストリームにプッシュしたり、空を確認したりできるようにするクラスを次に示します。これは、過去に非常に便利だとわかった、シンプルな実装を備えたシンプルなアイデアです。
class Pushable:
def __init__(self, iter):
self.source = iter
self.stored = []
def __iter__(self):
return self
def __bool__(self):
if self.stored:
return True
try:
self.stored.append(next(self.source))
except StopIteration:
return False
return True
def Push(self, value):
self.stored.append(value)
def peek(self):
if self.stored:
return self.stored[-1]
value = next(self.source)
self.stored.append(value)
return value
def __next__(self):
if self.stored:
return self.stored.pop()
return next(self.source)
beforeを使用する必要がある場合は、ジェネレーターを使用します。いいえ、簡単な方法はありません。 afterジェネレーターを使用するまで待つことができる場合、簡単な方法があります。
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()
isliceを使用すると、最初の反復までチェックするだけで、空かどうかを確認できます。
itertools import isliceから
def isempty(反復可能):
return list(islice(iterable、1))== []
ループが実行されるかどうかを確認するだけで、何かが得られたかどうかを確認しながらイテレータを返し続けるために使用する私の簡単なアプローチは次のとおりです:
n = 0
for key, value in iterator:
n+=1
yield key, value
if n == 0:
print ("nothing found in iterator)
break
ジェネレータを itertools.chain でラップし、反復可能の最後を表すものを2番目の反復可能として配置し、それを確認します。
例:
import itertools
g = some_iterable
eog = object()
wrap_g = itertools.chain(g, [eog])
あとは、iterableの最後に追加した値を確認するだけです。それを読むと、それが終了を意味します
for value in wrap_g:
if value == eog: # Ding DING! We just found the last element of the iterable
pass # Do something