Programing

생성기가 처음부터 비어 있는지 어떻게 알 수 있습니까?

lottogame 2020. 7. 10. 08:15
반응형

생성기가 처음부터 비어 있는지 어떻게 알 수 있습니까?


생성기에 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의 의견에서 지적 된 문제가 수정되었습니다.


IMHO의 최선의 방법은 특별한 테스트를 피하는 것입니다. 대부분의 시간, 발전기의 사용 이다 테스트는 :

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!"

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():
        ...

그게 당신의 경우이 작품이며, 발전기는 A로부터 오는 기능 처럼 generator().


나는 나 자신을 사용하지 않을 것이라고, 특히 한 두 번째 솔루션을 제공 싫지만, 당신은 절대적 경우 이 작업을 수행하고 다른 답변에서와 같이 발전기를 소비하지 :

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)

이제는이 솔루션이 마음에 들지 않습니다. 왜냐하면 이것이 발전기가 사용되는 방식이 아니라고 믿기 때문입니다.


명백한 접근 방식에 대해 죄송하지만 가장 좋은 방법은 다음과 같습니다.

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.


발전기가 비어 있는지 확인하기 위해해야 ​​할 일은 다음 결과를 얻는 것입니다. 물론 그 결과를 사용할 준비 가되지 않았다면 나중에 다시 반환하기 위해 저장해야합니다.

다음은 __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')

필자의 경우 항목을 병합하는 함수에 전달하기 전에 생성기 호스트가 채워 졌는지 알아야했습니다 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"

내 특정 문제에는 iterables가 비어 있거나 정확히 동일한 수의 항목이 있다는 속성이 있습니다.


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)

이 스레드에 빠졌고 매우 간단하고 읽기 쉬운 대답이 누락되었음을 깨달았습니다.

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]

If you need to know before you use the generator, then no, there is no simple way. If you can wait until after you have used the generator, there is a simple way:

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()

Here is my simple approach that i use to keep on returning an iterator while checking if something was yielded I just check if the loop runs:

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

Here's a simple decorator which wraps the generator, so it returns None if empty. This can be useful if your code needs to know whether the generator will produce anything before looping through it.

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

Usage:

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

One example where this is useful is in templating code - i.e. jinja2

{% if content_generator %}
  <section>
    <h4>Section title</h4>
    {% for item in content_generator %}
      {{ item }}
    {% endfor %
  </section>
{% endif %}

Simply wrap the generator with itertools.chain, put something that will represent the end of the iterable as the second iterable, then simply check for that.

Ex:

import itertools

g = some_iterable
eog = object()
wrap_g = itertools.chain(g, [eog])

Now all that's left is to check for that value we appended to the end of the iterable, when you read it then that will signify the end

for value in wrap_g:
    if value == eog: # DING DING! We just found the last element of the iterable
        pass # Do something

using islice you need only check up to the first iteration to discover if it is empty.

from itertools import islice

def isempty(iterable):
    return list(islice(iterable,1)) == []


What about using any()? I use it with generators and it's working fine. Here there is guy explaining a little about this


Use the peek function in cytoolz.

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

The iterator returned by this function will be equivalent to the original one passed in as an argument.


I solved it by using the sum function. See below for an example I used with glob.iglob (which returns a generator).

def isEmpty():
    files = glob.iglob(search)
    if sum(1 for _ in files):
        return True
    return False

*This will probably not work for HUGE generators but should perform nicely for smaller lists

참고URL : https://stackoverflow.com/questions/661603/how-do-i-know-if-a-generator-is-empty-from-the-start

반응형