Une seule ligne pour vérifier si un itérateur produit au moins un élément?

102

Actuellement, je fais ceci:

try:
    something = iterator.next()
    # ...
except StopIteration:
    # ...

Mais je voudrais une expression que je puisse placer dans un simple if déclaration. Y a-t-il quelque chose de intégré qui rendrait ce code moins maladroit?

any() Retour False si un itérable est vide, mais il itérera potentiellement sur tous les éléments si ce n'est pas le cas. Je n'en ai besoin que pour vérifier le premier élément.


Quelqu'un demande ce que j'essaye de faire. J'ai écrit une fonction qui exécute une requête SQL et donne ses résultats. Parfois, lorsque j'appelle cette fonction, je veux juste savoir si la requête a renvoyé quelque chose et prendre une décision en fonction de cela.

Bastien Léonard
la source
2
Un autre problème avec ce code est que vous ne pouvez pas le conditionner dans une fonction, car il consommera le premier élément. Bonne question.
andrewrk
2
Dans mon cas, je n'ai pas du tout besoin de l'élément, je veux juste savoir qu'il y a au moins un élément.
Bastien Léonard
2
hah! Mon même cas d'utilisation en essayant de trouver la même solution!
Daniel
En relation: stackoverflow.com/q/661603/281545
Mr_and_Mrs_D
1
Aussi lié: hasNext dans les itérateurs Python?
user2314737

Réponses:

134

anyn'ira pas au-delà du premier élément s'il est vrai. Au cas où l'itérateur donnerait quelque chose de faux, vous pouvez écrire any(True for _ in iterator).

Jochen Ritzel
la source
Cela semble fonctionner pour moi, avec un re.finditer. Vous pouvez tester que tout s'arrête au premier succès facilement: exécutez any((x > 100 for x in xrange(10000000)))puis exécutez any((x > 10000000 for x in xrange(100000000)))- le second devrait prendre beaucoup plus de temps.
chbrown
1
Cela fonctionne pour le cas de "au moins x"sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len
Dave Butler
11
De même si vous avez besoin de vérifier si l'itérateur est vide, on pourrait utiliser all(False for _ in iterator)va vérifier si l'itérateur est vide. (all retourne True si l'itérateur est vide, sinon il s'arrête lorsqu'il voit le premier élément False)
KGardevoir
23
Le gros problème avec cette solution est que vous ne pouvez pas réellement utiliser la valeur renvoyée par l'itérateur si elle n'est pas vide, non?
Ken Williams
42

En Python 2.6+, si le nom sentinelest lié à une valeur que l'itérateur ne peut pas fournir,

if next(iterator, sentinel) is sentinel:
    print('iterator was empty')

Si vous n'avez aucune idée de ce que l'itérateur pourrait éventuellement produire, créez votre propre sentinelle (par exemple en haut de votre module) avec

sentinel = object()

Sinon, vous pouvez utiliser, dans le rôle sentinelle, n'importe quelle valeur que vous «connaissez» (en fonction des considérations d'application) que l'itérateur ne peut pas fournir.

Alex Martelli
la source
1
Agréable! Pour mon cas d'utilisation, if not next(iterator, None):c'était suffisant car j'étais sûr qu'aucun ne ferait partie des éléments. Merci de m'avoir pointé dans la bonne direction!
wasabigeek
1
@wasabi N'oubliez pas que notretournera True pour tout objet faux, comme les listes vides, False et zéro. is not Noneest plus sûr et à mon avis plus clair.
Caagr98
21

Ce n'est pas vraiment plus propre, mais cela montre un moyen de le regrouper dans une fonction sans perte:

def has_elements(iter):
  from itertools import tee
  iter, any_check = tee(iter)
  try:
    any_check.next()
    return True, iter
  except StopIteration:
    return False, iter

has_el, iter = has_elements(iter)
if has_el:
  # not empty

Ce n'est pas vraiment pythonique, et pour des cas particuliers, il existe probablement de meilleures solutions (mais moins générales), comme la prochaine valeur par défaut.

first = next(iter, None)
if first:
  # Do something

Ce n'est pas général car None peut être un élément valide dans de nombreux itérables.

Matthew Flaschen
la source
C'est probablement la meilleure façon de procéder. Cependant, il serait utile de savoir ce que le PO tente de faire? Il y a probablement une solution plus élégante (c'est Python, après tout).
rossipedia
Merci, je pense que je vais utiliser next().
Bastien Léonard
1
@Bastien, d'accord, mais faites-le avec une sentinelle appropriée (voir ma réponse).
Alex Martelli
3
Il y a une énorme fuite de mémoire dans cette solution. Les teein itertools devront conserver chaque élément de l'itérateur d'origine au cas où il serait any_checknécessaire d'avancer. C'est pire que de simplement convertir l'itérateur d'origine en liste.
Rafał Dowgird
1
@ RafałDowgird C'est pire que de simplement convertir l'itérateur d'origine en liste. Pas vraiment - pensez à des séquences infinies.
Piotr Dobrogost
6

vous pouvez utiliser:

if zip([None], iterator):
    # ...
else:
    # ...

mais c'est un peu non explicatif pour le lecteur de code

mykhal
la source
2
.. (vous pouvez utiliser n'importe quel élément itérable à 1 élément au lieu de [Aucun])
mykhal
5

La meilleure façon de le faire est d'utiliser un peekablefichier from more_itertools.

from more_itertools import peekable
iterator = peekable(iterator)
if iterator:
    # Iterator is non-empty.
else:
    # Iterator is empty.

Méfiez-vous simplement si vous avez conservé les références de l'ancien itérateur, cet itérateur deviendra avancé. Vous devez utiliser le nouvel itérateur visible à partir de là. Vraiment, cependant, peekable s'attend à être le seul morceau de code modifiant cet ancien itérateur, vous ne devriez donc pas garder les références de l'ancien itérateur de toute façon.

énigmatiquePhysicien
la source
3

Qu'en est-il de:

In [1]: i=iter([])

In [2]: bool(next(i,False))
Out[2]: False

In [3]: i=iter([1])

In [4]: bool(next(i,False))
Out[4]: True
Neil
la source
4
Intéressant! Mais que se passe-t-il si next () renvoie False parce que c'est ce qui a vraiment été produit?
Bastien Léonard
@ BastienLéonard Créer une classe class NotSet: pass, puis vérifierif next(i, NotSet) is NotSet: print("Iterator is empty")
Elijas
-1

__length_hint__ estime la longueur de list(it)- c'est une méthode privée, cependant:

x = iter( (1, 2, 3) )
help(x.__length_hint__)
      1 Help on built-in function __length_hint__:
      2 
      3 __length_hint__(...)
      4     Private method returning an estimate of len(list(it)).
Miku
la source
4
pas garanti pour chaque itérateur. >>> def it (): ... yield 1 ... yield 2 ... yield 3 ... >>> i = it () >>> i .__ length_hint__ Traceback (dernier appel en dernier): File " <stdin> ", ligne 1, dans <module> AttributeError: l'objet 'generator' n'a pas d'attribut ' length_hint '
andrewrk
3
Il est également probablement légal qu'il renvoie 0 pour un itérateur qui a plus de zéro entrée, car ce n'est qu'un indice.
Glenn Maynard
-1

C'est un wrapper d'itérateur excessif qui permet généralement de vérifier s'il y a un élément suivant (via la conversion en booléen). Bien sûr assez inefficace.

class LookaheadIterator ():

    def __init__(self, iterator):
        self.__iterator = iterator
        try:
            self.__next      = next (iterator)
            self.__have_next = True
        except StopIteration:
            self.__have_next = False

    def __iter__(self):
        return self

    def next (self):
        if self.__have_next:
            result = self.__next
            try:
                self.__next      = next (self.__iterator)
                self.__have_next = True
            except StopIteration:
                self.__have_next = False

            return result

        else:
            raise StopIteration

    def __nonzero__(self):
        return self.__have_next

x = LookaheadIterator (iter ([]))
print bool (x)
print list (x)

x = LookaheadIterator (iter ([1, 2, 3]))
print bool (x)
print list (x)

Production:

False
[]
True
[1, 2, 3]
double
la source
-2

Un peu tard, mais ... Vous pouvez transformer l'itérateur en liste et travailler avec cette liste:

# Create a list of objects but runs out the iterator.
l = [_ for _ in iterator]

# If the list is not empty then the iterator had elements; else it was empty.
if l :
    pass # Use the elements of the list (i.e. from the iterator)
else :
    pass # Iterator was empty, thus list is empty.
Jens
la source
4
Ceci est inefficace car il énumère la liste entière. Ne fonctionnera pas pour les générateurs infinis.
becko
@becko: d'accord. Mais cela ne semble pas être le cas dans la question initiale.
Jens
3
Un autre problème est que l'itérateur pourrait générer une quantité infinie d'objets qui peuvent entraîner un débordement de mémoire, et le fait que le programme n'atteindra jamais l'instruction suivante
Willem Van Onsem