Una riga per verificare se un iteratore produce almeno un elemento?


101

Attualmente sto facendo questo:

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

Ma vorrei un'espressione che posso inserire in una semplice ifdichiarazione. C'è qualcosa di integrato che renderebbe questo codice meno goffo?

any()restituisce Falsese un iterabile è vuoto, ma potenzialmente itererà su tutti gli elementi se non lo è. Mi serve solo per controllare il primo elemento.


Qualcuno mi chiede cosa sto cercando di fare. Ho scritto una funzione che esegue una query SQL e produce i suoi risultati. A volte, quando chiamo questa funzione, voglio solo sapere se la query ha restituito qualcosa e prendere una decisione in base a ciò.


2
Inoltre un problema con quel codice è che non puoi impacchettarlo in una funzione, perché mangerà il primo elemento. Buona domanda.
andrewrk

2
Nel mio caso non ho affatto bisogno dell'elemento, voglio solo sapere che c'è almeno un elemento.
Bastien Léonard

2
ah! Il mio stesso caso d'uso nel cercare di trovare la stessa soluzione!
Daniel


Risposte:


134

anynon andrà oltre il primo elemento se è vero. Nel caso in cui l'iteratore produca qualcosa di falso, puoi scrivere any(True for _ in iterator).


Questo sembra funzionare per me, con un re.finditer. Puoi verificare facilmente che qualsiasi arresto al primo successo: corri any((x > 100 for x in xrange(10000000)))e poi corri any((x > 10000000 for x in xrange(100000000)))- il secondo dovrebbe richiedere molto più tempo.
chbrown

1
Questo funziona per il caso di "almeno x"sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len
Dave Butler

11
Allo stesso modo, se è necessario controllare se l'iteratore è vuoto, si potrebbe usare all(False for _ in iterator)controllerà se l'iteratore è vuoto. (tutto restituisce True se l'iteratore è vuoto, altrimenti si ferma quando vede il primo elemento False)
KGardevoir

22
Il grosso problema con questa soluzione è che non puoi effettivamente utilizzare il valore restituito dall'iteratore se non è vuoto, giusto?
Ken Williams

42

In Python 2.6+, se name sentinelè vincolato a un valore che l'iteratore non può produrre,

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

Se non hai idea di cosa potrebbe produrre l'iteratore, crea la tua sentinella (ad esempio all'inizio del modulo) con

sentinel = object()

Altrimenti, potresti usare, nel ruolo di sentinella, qualsiasi valore che "conosci" (in base a considerazioni sull'applicazione) che l'iteratore non può cedere.


1
Bello! Per il mio caso d'uso, if not next(iterator, None):era sufficiente perché ero sicuro che Nessuno non avrebbe fatto parte degli elementi. Grazie per avermi indicato la giusta direzione!
wasabigeek

1
@wasabi Ricorda che notrestituirà True per qualsiasi oggetto falso, come elenchi vuoti, False e zero. is not Noneè più sicuro, e secondo me più chiaro.
Caagr98,

21

Questo non è molto più pulito, ma mostra un modo per impacchettarlo in una funzione senza perdite:

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

Questo non è realmente pitonico e, per casi particolari, ci sono probabilmente soluzioni migliori (ma meno generali), come il prossimo default.

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

Questo non è generale perché Nessuno può essere un elemento valido in molti iterabili.


Questo è probabilmente il modo migliore per farlo. Tuttavia, sarebbe utile sapere cosa sta cercando di fare l'OP? Probabilmente c'è una soluzione più elegante (questo è Python, dopotutto).
rossipedia

Grazie, penso che userò next().
Bastien Léonard

1
@Bastien, bene, ma fallo con una sentinella appropriata (vedi la mia risposta).
Alex Martelli

3
C'è un'enorme perdita di memoria in questa soluzione. Gli teein itertools dovranno mantenere ogni singolo elemento dell'iteratore originale nel caso in cui any_checksia necessario avanzare. Questo è peggio della semplice conversione dell'iteratore originale in un elenco.
Rafał Dowgird

1
@ RafałDowgird Questo è peggio della semplice conversione dell'iteratore originale in un elenco. Non proprio: pensa alle sequenze infinite.
Piotr Dobrogost

6

Puoi usare:

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

ma è un po 'poco esplicativo per il lettore di codice


2
.. (puoi usare qualsiasi iterabile di 1 elemento invece di [Nessuno])
mykhal

5

Il modo migliore per farlo è con un peekablefrom more_itertools.

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

Fai solo attenzione se hai mantenuto i riferimenti al vecchio iteratore, quell'iteratore diventerà avanzato. Da quel momento in poi dovrai utilizzare il nuovo iteratore visualizzabile. In realtà, però, peekable si aspetta di essere l'unico bit di codice che modifica quel vecchio iteratore, quindi non dovresti comunque mantenere i riferimenti al vecchio iteratore in giro.


3

Che dire:

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

4
Interessante! Ma cosa succede se next () restituisce False perché è ciò che è stato realmente prodotto?
Bastien Léonard

@ BastienLéonard Crea un corso class NotSet: pass, quindi verifica la presenza diif next(i, NotSet) is NotSet: print("Iterator is empty")
Elijas

-1

__length_hint__ stima la durata di list(it)- è un metodo privato, però:

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

4
non garantito per ogni iteratore. >>> def it (): ... rendimento 1 ... rendimento 2 ... rendimento 3 ... >>> i = it () >>> i .__ length_hint__ Traceback (la chiamata più recente per ultima): File " <stdin> ", riga 1, in <module> AttributeError: l'oggetto 'generator' non ha attributo ' length_hint '
andrewrk

3
Probabilmente è anche legale restituire 0 per un iteratore che ha più di zero voci, poiché è solo un suggerimento.
Glenn Maynard

-1

Questo è un wrapper iteratore eccessivo che generalmente consente di verificare se c'è un elemento successivo (tramite la conversione in booleano). Ovviamente piuttosto inefficiente.

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)

Produzione:

False
[]
True
[1, 2, 3]

-2

Un po 'tardi, ma ... potresti trasformare l'iteratore in una lista e poi lavorare con quella lista:

# 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.

4
Questo è inefficiente perché enumera l'intero elenco. Non funzionerà per generatori infiniti.
Becko

@becko: d'accordo. Ma non sembra essere il caso della domanda originale.
Jens

3
Un altro problema è che l'iteratore potrebbe generare una quantità infinita di oggetti che possono provocare un overflow della memoria e il fatto che il programma non raggiungerà mai l'istruzione successiva
Willem Van Onsem
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.