Question

Existe-t-il un moyen simple de vérifier si le générateur ne contient aucun élément, par exemple, peek, hasNext, isEmpty, etc.?

Était-ce utile?

La solution

La réponse simple à votre question: non, il n’ya pas de solution simple. Il y a beaucoup de solutions de rechange.

Il ne devrait pas y avoir de moyen simple, à cause de ce que sont les générateurs: un moyen de générer une séquence de valeurs sans conserver la séquence en mémoire . Donc, il n'y a pas de traversée en arrière.

Vous pouvez écrire une fonction has_next ou même l'adapter à un générateur en tant que méthode avec un décorateur de fantaisie si vous le souhaitez.

Autres conseils

Suggestion:

def peek(iterable):
    try:
        first = next(iterable)
    except StopIteration:
        return None
    return first, itertools.chain([first], iterable)

Utilisation:

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.

Une méthode simple consiste à utiliser le paramètre facultatif de next () qui est utilisé si le générateur est épuisé (ou vide). Par exemple:

iterable = some_generator()

_exhausted = object()

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

Edit: Correction du problème signalé dans le commentaire de mehtunguh.

La meilleure approche, à mon humble avis, serait d’éviter un test spécial. La plupart du temps, l’utilisation d’un générateur est le test:

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)

Si cela ne suffit pas, vous pouvez toujours effectuer un test explicite. À ce stade, thing contiendra la dernière valeur générée. Si rien n'a été généré, ce sera indéfini - à moins que vous n'ayez déjà défini la variable. Vous pouvez vérifier la valeur de <=>, mais c'est un peu peu fiable. Au lieu de cela, définissez simplement un drapeau dans le bloc et vérifiez-le ensuite:

if not thing_generated:
    print "Avast, ye scurvy dog!"

next(generator, None) is not None

Ou remplacez None mais quelle que soit la valeur, vous savez que ce n'est pas dans votre générateur.

Modifier : oui, un élément du générateur sera ignoré. Cependant, souvent, je vérifie si un générateur est vide uniquement à des fins de validation, alors ne l'utilisez pas vraiment. Ou sinon je fais quelque chose comme:

def foo(self):
    if next(self.my_generator(), None) is None:
        raise Exception("Not initiated")

    for x in self.my_generator():
        ...

C'est-à-dire que cela fonctionne si votre générateur provient d'une fonction , comme dans generator().

Je déteste proposer une deuxième solution, en particulier une solution que je ne voudrais pas utiliser moi-même, mais si vous deviez absolument le faire et ne pas consommer le générateur, comme dans d'autres réponses:

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)

Maintenant, je n'aime vraiment pas cette solution, car je crois que ce n'est pas ainsi que les générateurs doivent être utilisés.

Désolé pour l'approche évidente, mais le meilleur moyen serait de le faire:

for item in my_generator:
     print item

Vous avez maintenant détecté que le générateur est vide pendant son utilisation. Bien sûr, l'élément ne sera jamais affiché si le générateur est vide.

Cela peut ne pas correspondre parfaitement à votre code, mais c’est la raison pour laquelle le générateur est utilisé: itérer, alors vous pourriez peut-être modifier légèrement votre approche ou ne pas utiliser de générateurs.

Je me rends compte que ce poste a 5 ans à ce stade-ci, mais je l’ai trouvé en cherchant un moyen idiomatique de le faire et je n’ai pas vu ma solution publiée. Donc pour la postérité:

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)

Bien sûr, comme de nombreux commentateurs le diront sûrement, il s’agit d’un hacky qui ne fonctionne que dans certaines situations limitées (où les générateurs sont exempts d’effets secondaires, par exemple). YMMV.

Tout ce que vous avez à faire pour voir si un générateur est vide est d’essayer d’obtenir le résultat suivant. Bien sûr, si vous n'êtes pas prêt à utiliser ce résultat, vous devez le stocker pour le renvoyer ultérieurement.

Voici une classe wrapper qui peut être ajoutée à un itérateur existant pour ajouter un test __nonzero__, afin que vous puissiez voir si le générateur est vide avec un simple if. On peut probablement aussi en faire un décorateur.

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)

Voici comment vous l'utiliseriez:

with open(filename, 'r') as f:
    f = GenWrapper(f)
    if f:
        print 'Not empty'
    else:
        print 'Empty'

Notez que vous pouvez vérifier le vide à tout moment, pas seulement au début de l'itération.

>>> gen = (i for i in [])
>>> next(gen)
Traceback (most recent call last):
  File "<pyshell#43>", line 1, in <module>
    next(gen)
StopIteration

À la fin du générateur StopIteration est levé, puisque dans votre cas, la fin est atteinte immédiatement, une exception est levée. Mais normalement, vous ne devriez pas vérifier l'existence de la valeur suivante.

Une autre chose que vous pouvez faire est:

>>> gen = (i for i in [])
>>> if not list(gen):
    print('empty generator')

Dans mon cas, j’avais besoin de savoir si une multitude de générateurs étaient remplis avant de les transmettre à une fonction qui fusionnait les éléments, c.-à-d. zip(...). La solution est similaire mais assez différente de la réponse acceptée:

Définition:

def has_items(iterable):
    try:
        return True, itertools.chain([next(iterable)], iterable)
    except StopIteration:
        return False, []

Utilisation:

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"

Mon problème particulier a la propriété que les itérables sont vides ou ont exactement le même nombre d'entrées.

Vient juste de tomber sur ce fil et s'est rendu compte qu'il manquait une réponse très simple et facile à lire:

def is_empty(generator):
    for item in generator:
        return False
    return True

Si nous ne supposons consommer aucun article, nous devons réinjecter le premier article dans le générateur:

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

Exemple:

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

Si vous devez savoir avant d'utiliser le générateur, alors non, il n'y a pas de solution simple. Si vous pouvez attendre que après que vous ayez utilisé le générateur, il existe un moyen simple:

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

Voici mon approche simple que j'utilise pour continuer à renvoyer un itérateur tout en vérifiant si quelque chose a été cédé Je vérifie juste si la boucle tourne:

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

Voici un simple décorateur qui enveloppe le générateur afin qu'il ne retourne aucun s'il est vide. Cela peut être utile si votre code a besoin de savoir si le générateur produira quoi que ce soit avant que ne le répète pas.

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

Utilisation:

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

Cela est utile, par exemple, dans les modèles de code - c.-à-d. jinja2

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

Emballez simplement le générateur avec itertools.chain , mettez quelque chose qui représente la fin de l'itérable comme deuxième itérable, puis vérifiez-le simplement.

Ex:

import itertools

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

Maintenant, il ne reste plus qu'à vérifier la valeur ajoutée à la fin de l'itérable. Lorsque vous la lirez, cela signifiera la fin

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

en utilisant islice, il vous suffit de vérifier jusqu'à la première itération pour savoir s'il est vide.

  

à partir d'itlice d'importation d'itertools

     

def isempty (iterable):
  & nbsp; & nbsp; & nbsp; & nbsp; liste de retour (islice (iterable, 1)) == []

Qu'en est-il d'utiliser any ()? Je l'utilise avec des générateurs et ça fonctionne bien. Ici , il y a un gars qui explique un peu ce

Utilisez la fonction peek dans 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

L'itérateur renvoyé par cette fonction sera équivalent à l'original transmis en tant qu'argument.

Invité par Mark Ransom, voici une classe que vous pouvez utiliser pour envelopper tout itérateur de manière à pouvoir regarder en avant, à renvoyer des valeurs dans le flux et à vérifier si elles sont vides. C’est une idée simple avec une implémentation simple que j’ai trouvée très pratique dans le passé.

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)

Je l'ai résolu en utilisant la fonction somme. Voir ci-dessous un exemple que j'ai utilisé avec glob.iglob (qui renvoie un générateur).

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

* Cela ne fonctionnera probablement pas pour les énormes générateurs, mais devrait fonctionner correctement pour les petites listes

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top