web-dev-qa-db-ja.com

ジェネレーターが最初から空かどうかを知るにはどうすればよいですか?

ジェネレーターにアイテムがないか、peek、hasNext、isEmptyなどの行に沿って何かをテストする簡単な方法はありますか?

119
Dan

あなたの質問に対する簡単な答え:いいえ、簡単な方法はありません。多くの回避策があります。

ジェネレーターとは何かという理由で、単純な方法は実際にはないはずです。値のシーケンスを出力する方法メモリにシーケンスを保持せずに。したがって、後方への走査はありません。

Has_next関数を書くこともできれば、必要に応じて派手なデコレータを使用したメソッドとしてジェネレーターにスラップすることもできます。

47
David Berger

提案:

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.
84
John Fouhy

簡単な方法は、 next() のオプションのパラメーターを使用することです。これは、ジェネレーターが使い果たされた(または空の)場合に使用されます。例えば:

iterable = some_generator()

_exhausted = object()

if next(iterable, _exhausted) == _exhausted:
    print('generator is empty')

編集:mehtunguhのコメントで指摘された問題を修正しました。

25
razz0

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()のように、generatorfunctionに由来する場合に機能します。

9
juanmirocks

私見である最善のアプローチは、特別なテストを避けることです。ほとんどの場合、ジェネレーターの使用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!"
9
vezult

私は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)

今、私はこのソリューションが本当に好きではありません。なぜなら、これはジェネレーターの使用方法ではないと考えているからです。

8
Ali Afshar

ジェネレータが空かどうかを確認するために必要なことは、次の結果を取得することだけです。もちろん、その結果を使用するために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'

反復の開始時だけでなく、いつでも空をチェックできることに注意してください。

3
Mark Ransom

明らかなアプローチで申し訳ありませんが、最善の方法はそうすることです。

for item in my_generator:
     print item

これで、使用中にジェネレーターが空であることを検出できました。もちろん、ジェネレーターが空の場合、アイテムは表示されません。

これはコードに正確に適合しない可能性がありますが、これはジェネレーターのイディオムです:反復。

3
Ali Afshar

私はこの投稿がこの時点で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。

3

私の場合、ジェネレーターのホストが関数に渡される前に生成されたかどうかを知る必要がありました。関数はアイテム、つまり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"

私の特定の問題には、反復可能オブジェクトが空であるか、エントリの数がまったく同じであるという特性があります。

1
>>> 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')
1
SilentGhost

ちょうどこのスレッドに落ちて、非常にシンプルで読みやすい答えが欠けていることに気付きました:

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]
1
Romaric

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

この関数によって返される反復子は、引数として渡される元の反復子と同等になります。

0
W.P. McNeill

以下はジェネレーターをラップするシンプルなデコレーターです。空の場合は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 %}
0
Greg

Any()の使用はどうですか?私は発電機でそれを使用し、それはうまく機能しています。 ここ これについて少し説明する人がいます

0
Sam

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)
0
sfkleach

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()
0
Ethan Furman

isliceを使用すると、最初の反復までチェックするだけで、空かどうかを確認できます。

itertools import isliceから

def isempty(反復可能):
return list(islice(iterable、1))== []

0
Quin

ループが実行されるかどうかを確認するだけで、何かが得られたかどうかを確認しながらイテレータを返し続けるために使用する私の簡単なアプローチは次のとおりです:

        n = 0
        for key, value in iterator:
            n+=1
            yield key, value
        if n == 0:
            print ("nothing found in iterator)
            break
0
PlagTag

ジェネレータを 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
0
smac89