Jedna linijka, aby sprawdzić, czy iterator daje co najmniej jeden element?


102

Obecnie robię to:

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

Ale chciałbym wyrażenia, które mogę umieścić w prostym ifstwierdzeniu. Czy jest coś wbudowanego, co sprawiłoby, że ten kod wyglądałby mniej niezgrabnie?

any()zwraca, Falsejeśli element iterowalny jest pusty, ale potencjalnie będzie iterował po wszystkich elementach, jeśli tak nie jest. Potrzebuję go tylko do sprawdzenia pierwszej pozycji.


Ktoś pyta, co próbuję zrobić. Napisałem funkcję, która wykonuje zapytanie SQL i daje jej wyniki. Czasami, gdy wywołuję tę funkcję, chcę tylko wiedzieć, czy zapytanie zwróciło cokolwiek, i na tej podstawie podjąć decyzję.


2
Problem z tym kodem polega również na tym, że nie można go spakować do funkcji, ponieważ zjada pierwszy element. Dobre pytanie.
andrewrk

2
W moim przypadku w ogóle nie potrzebuję elementu, chcę tylko wiedzieć, że jest co najmniej jeden element.
Bastien Léonard

2
hah! Mój ten sam przypadek użycia, gdy próbuję znaleźć to samo rozwiązanie!
Daniel


Odpowiedzi:


134

anynie wykroczy poza pierwszy element, jeśli to prawda. W przypadku, gdy iterator daje coś fałszywego, możesz napisać any(True for _ in iterator).


Wydaje się, że to działa dla mnie, z re.finditerem. Możesz łatwo sprawdzić, czy każdy zatrzymuje się przy pierwszym sukcesie: biegnij, any((x > 100 for x in xrange(10000000)))a potem biegnij any((x > 10000000 for x in xrange(100000000)))- drugi powinien zająć znacznie dłużej.
chbrown

1
Działa to w przypadku „co najmniej x”sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len
Dave Butler,

11
Podobnie, jeśli chcesz sprawdzić, czy iterator jest pusty, możesz użyć all(False for _ in iterator)sprawdzenia, czy iterator jest pusty. (all zwraca True, jeśli iterator jest pusty, w przeciwnym razie zatrzymuje się, gdy zobaczy pierwszy element False)
KGardevoir

23
Duży problem z tym rozwiązaniem polega na tym, że nie można w rzeczywistości użyć wartości zwróconej przez iterator, jeśli nie jest on pusty, prawda?
Ken Williams,

42

W Pythonie 2.6+, jeśli nazwa sentineljest powiązana z wartością, której iterator nie może uzyskać,

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

Jeśli nie masz pojęcia, co może dać iterator, stwórz swój własny wartownik (np. Na górze modułu) z

sentinel = object()

W przeciwnym razie możesz użyć w roli wartownika dowolnej wartości, którą „znasz” (na podstawie rozważań aplikacji), a której iterator nie może uzyskać.


1
Miły! W moim przypadku if not next(iterator, None):było wystarczające, ponieważ byłem pewien, że Żaden nie będzie częścią elementów. Dzięki za wskazanie mi właściwego kierunku!
wasabigeek

1
@wasabi Pamiętaj, że notzwróci True dla każdego fałszywego obiektu, takiego jak puste listy, False i zero. is not Nonejest bezpieczniejszy i moim zdaniem jaśniejszy.
Caagr98

21

Nie jest to naprawdę czystsze, ale pokazuje sposób na bezstratne spakowanie go w funkcję:

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

To nie jest do końca Pythonowe, aw niektórych przypadkach prawdopodobnie istnieją lepsze (ale mniej ogólne) rozwiązania, takie jak następne domyślne.

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

Nie jest to ogólne, ponieważ None może być prawidłowym elementem w wielu iterowalnych.


To prawdopodobnie najlepszy sposób na zrobienie tego. Pomogłoby jednak wiedzieć, co próbuje zrobić PO? Prawdopodobnie jest bardziej eleganckie rozwiązanie (w końcu to jest Python).
rossipedia

Dzięki, myślę, że skorzystam next().
Bastien Léonard

1
@Bastien, dobrze, ale zrób to z odpowiednim wartownikiem (zobacz moją odpowiedź).
Alex Martelli

3
W tym rozwiązaniu występuje ogromny wyciek pamięci. W teeitertools będą musiały zachować każdy pojedynczy element z oryginalnego iteratora na wypadek, gdyby any_checkkiedykolwiek trzeba było przejść dalej. Jest to gorsze niż zwykłe przekonwertowanie oryginalnego iteratora na listę.
Rafał Dowgird

1
@ RafałDowgird To jest gorsze niż zwykłe przekonwertowanie oryginalnego iteratora na listę. Nie bardzo - pomyśl o nieskończonych sekwencjach.
Piotr Dobrogost

6

możesz użyć:

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

ale jest to trochę niewyjaśnione dla czytnika kodów


2
.. (zamiast [Brak] można użyć dowolnej
iteracji

5

Najlepszym sposobem na to jest użycie peekableod more_itertools.

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

Po prostu uważaj, jeśli zachowałeś odniesienia do starego iteratora, że ​​iterator będzie zaawansowany. Od tego momentu musisz używać nowego iteratora podglądu. Naprawdę jednak, peekable spodziewa się, że będzie jedynym fragmentem kodu modyfikującym ten stary iterator, więc i tak nie powinieneś przechowywać referencji do starego iteratora.


3

Co powiesz na:

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
Ciekawe! Ale co się stanie, jeśli next () zwróci wartość False, ponieważ tak naprawdę została uzyskana?
Bastien Léonard

@ BastienLéonard Utwórz zajęcia class NotSet: pass, a następnie sprawdźif next(i, NotSet) is NotSet: print("Iterator is empty")
Elijas

-1

__length_hint__ szacuje długość list(it)- to jednak metoda prywatna:

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
nie jest gwarantowane dla każdego iteratora. >>> def it (): ... yield 1 ... yield 2 ... yield 3 ... >>> i = it () >>> i .__ length_hint__ Traceback (ostatnie wywołanie last): File " <stdin> ", wiersz 1, w <module> AttributeError: obiekt„ generator ”nie ma atrybutu„ length_hint
andrewrk

3
Prawdopodobnie jest również legalne, aby zwracać 0 dla iteratora, który ma więcej niż zero wpisów, ponieważ jest to tylko wskazówka.
Glenn Maynard

-1

Jest to overkill iterator wrapper, który generalnie pozwala sprawdzić, czy istnieje następny element (poprzez konwersję na boolean). Oczywiście dość nieefektywne.

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)

Wynik:

False
[]
True
[1, 2, 3]

-2

Trochę późno, ale ... Możesz zmienić iterator w listę, a następnie pracować z tą listą:

# 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
Jest to nieefektywne, ponieważ wylicza całą listę. Nie będzie działać dla nieskończonych generatorów.
becko

@becko: Zgoda. Ale wydaje się, że tak nie jest w pierwotnym pytaniu.
Jens

3
Innym problemem jest to, że iterator może wygenerować nieskończoną liczbę obiektów, co może spowodować przepełnienie pamięci oraz fakt, że program nigdy nie dotrze do następnej instrukcji
Willem Van Onsem
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.