Warum gibt es in Python keine erste (iterierbare) integrierte Funktion?

74

Ich frage mich, ob es einen Grund gibt, warum es first(iterable)in den in Python integrierten Funktionen keine gibt , ähnlich wie any(iterable)und all(iterable)(es kann irgendwo in einem stdlib-Modul versteckt sein, aber ich sehe es nicht in itertools). firstwürde eine Kurzschlussgeneratorauswertung durchführen, so dass unnötige (und möglicherweise unendlich viele) Operationen vermieden werden können; dh

def identity(item):
    return item

def first(iterable, predicate=identity):
    for item in iterable:
        if predicate(item):
            return item
    raise ValueError('No satisfactory value found')

Auf diese Weise können Sie Dinge ausdrücken wie:

denominators = (2, 3, 4, 5)
lcd = first(i for i in itertools.count(1)
    if all(i % denominators == 0 for denominator in denominators))

Offensichtlich können Sie nicht tun , list(generator)[0]in diesem Fall, da der Generator nicht beendet.

Oder wenn Sie eine Reihe von regulären Ausdrücken haben, mit denen Sie übereinstimmen können (nützlich, wenn alle dieselbe groupdictBenutzeroberfläche haben):

match = first(regex.match(big_text) for regex in regexes)

Sie sparen viel unnötige Verarbeitung, indem Sie list(generator)[0]eine positive Übereinstimmung vermeiden und kurzschließen.

cdleary
quelle
Nur eine Anmerkung: Mir ist klar, dass das Prädikat kwarg mit den Generatorfunktionen redundant ist. Ich wollte nur gründlich definieren, was "zuerst" wirklich bedeutet.
cdleary

Antworten:

49

Wenn Sie einen Iterator haben, können Sie einfach dessen nextMethode aufrufen . Etwas wie:

In [3]: (5*x for x in xrange(2,4)).next()
Out[3]: 10
liori
quelle
13
Die obige Methode funktioniert nicht in Python 3, verwenden Sie, next(x)wenn xes sich um einen Iterator handelt oder next(iter(d))wenn des iterierbar ist
Taha Jahangir
1
Eine allgemeine Lösung für alle iterablen Elemente (z. B. für Listen und Generatoren) in Python 2.6 und höher ist next(iter(xs)). In Python 2.5 können Sie dies tun iter(xs).next().
Sah
9
Ich verstehe diese Antwort nicht. Das in der Frage gezeigte 'Erste' überspringt die Anfangselemente der Sequenz, die 'falsch' sind (wie durch bool (Prädikat (Element)) definiert). Ich dachte das wäre der Punkt. 'next ()' macht das nicht. Ich bin verwirrt.
Jonathan Hartley
2
@JonathanHartley: Der Punkt ist, dass next () und eine generische Methode zum Erstellen einer gefilterten Sequenz (z. B. Verwenden itertools.ifilter()oder (… for … in … if condition)Kombinieren) nicht ausreichen, um die Verwendung eines anderen integrierten Tools zu rechtfertigen. Beachten Sie, dass das Regex-Beispiel von OP nur next(regex for regex in regexes if regex.match(big_text)).
liori ist
4
Das Beispiel von OP gibt etwas anderes zurück als next(regex for regex in regexes if regex.match(big_text)); Es gibt die Ergebnisse von regex.match (big_text) zurück. Wie macht man das ohne first ()? next(regex.match(big_text) for regex in regexes if regex.match(big_text))ist redundant. next(ifilter(imap(lambda x: x.match(big_text), regexes)))scheint im Vergleich zu zuerst zu komplex.
pjz
14

Es gibt ein Pypi-Paket namens "first" , das dies tut:

>>> from first import first
>>> first([0, None, False, [], (), 42])
42

So würden Sie beispielsweise die erste ungerade Zahl zurückgeben:

>> first([2, 14, 7, 41, 53], key=lambda x: x % 2 == 1)
7

Wenn Sie nur das erste Element vom Iterator zurückgeben möchten, unabhängig davon, ob es wahr ist oder nicht, gehen Sie folgendermaßen vor:

>>> first([0, None, False, [], (), 42], key=lambda x: True)
0

Es ist ein sehr kleines Paket: Es enthält nur diese Funktion, hat keine Abhängigkeiten und funktioniert unter Python 2 und 3. Es ist eine einzelne Datei, sodass Sie sie nicht einmal installieren müssen, um sie zu verwenden.

Tatsächlich ist hier fast der gesamte Quellcode (ab Version 2.0.1 von Hynek Schlawack, veröffentlicht unter der MIT-Lizenz):

def first(iterable, default=None, key=None):
    if key is None:
        for el in iterable:
            if el:
                return el
    else:
        for el in iterable:
            if key(el):
                return el
    return default
Flimm
quelle
5
Nett. Die Implementierung selbst erfordert jedoch etwa drei Codezeilen. Dies rechtfertigt kaum den Aufwand für die Installation eines Komplettpakets (Einführung aller Portabilitätsprobleme usw.). Die Frage bleibt: Warum ist dieser Teil nicht Teil der in Python integrierten Funktionen? Oder was ist die sauberste und pythonischste Art, dies mithilfe integrierter Python-Strukturen zu formulieren?
Alfe
2
@ Alfe: Die Verwendung von Paketen ist sauber und pythonisch. Warum es kein eingebautes System ist, ist für Stack Overflow keine Frage, da es möglicherweise von niemandem beantwortet werden kann, der kein Core Committer ist.
Flimm
2
Okay, dann lassen Sie es mich so sagen: Wie würden Sie die firstFunktion vom Modul aus implementieren first? Wenn ich frage, warum dies nicht integriert ist, mache ich das, weil ich vermute, dass es eine pythonische Möglichkeit gibt, dies mit allgemeineren Funktionen wie Listenverständnis usw. auszudrücken, die es überflüssig genug machen, es wegzulassen.
Alfe
2
@ Alfie: Fair genug. Die anderen Antworten versuchen dies zu tun, aber wie Sie sehen können, ist das Ergebnis nicht so hübsch, und ich bin sicher, dass einige Leute, die in der Lage sind, Module einfach zu installieren, dies firstnützlich finden werden . Ich habe den Quellcode für die Funktion für Interesse aufgenommen.
Flimm
"Aber die Implementierung selbst dauert ungefähr drei Codezeilen" -> Nun, ich habe mir mindestens ein Dutzend Antworten auf diese Funktion angesehen, und alle scheinen mindestens ein paar Zeilen mit einer Schleife oder einer Funktion zu enthalten mit Ertrag oder was auch immer scheinbar magischer pythonischer Trick von einem Python-Neuling. In Ruby machst du mylist.first oder myiterator.first und es funktioniert. Einfach, fehlerfrei und am besten lesbar
Alex F
11

Ich habe kürzlich eine ähnliche Frage gestellt (sie wurde inzwischen als Duplikat dieser Frage markiert). Meine Sorge war auch, dass ich eingebaute Geräte nur verwenden wollte , um das Problem zu lösen, den ersten wahren Wert eines Generators zu finden. Meine eigene Lösung war dann folgende:

x = next((v for v in (f(x) for x in a) if v), False)

Für das Beispiel des Findens der ersten regulären Ausdrucksübereinstimmung (nicht des ersten übereinstimmenden Musters!) Würde dies folgendermaßen aussehen:

patterns = [ r'\d+', r'\s+', r'\w+', r'.*' ]
text = 'abc'
firstMatch = next(
  (match for match in
    (re.match(pattern, text) for pattern in patterns)
   if match),
  False)

Das Prädikat wird nicht zweimal ausgewertet (wie Sie es tun müssten, wenn nur das Muster zurückgegeben würde), und es werden keine Hacks wie Einheimische im Verständnis verwendet.

Es sind jedoch zwei Generatoren verschachtelt, bei denen die Logik vorschreibt, nur einen zu verwenden. Eine bessere Lösung wäre also schön.

Alfe
quelle
6

Es gibt einen "Slice" -Iterator in itertools. Es emuliert die Slice-Operationen, mit denen wir in Python vertraut sind. Was Sie suchen, ist etwas Ähnliches:

myList = [0,1,2,3,4,5]
firstValue = myList[:1]

Das Äquivalent mit itertools für Iteratoren:

from itertools import islice
def MyGenFunc():
    for i in range(5):
        yield i

mygen = MyGenFunc()
firstValue = islice(mygen, 0, 1)
print firstValue 
Zoran Pavlovic
quelle
6

Ihre Frage enthält einige Unklarheiten. Ihre Definition von first und das Regex-Beispiel implizieren, dass es einen booleschen Test gibt. Das Nennerbeispiel enthält jedoch explizit eine if-Klausel. Es ist also nur ein Zufall, dass jede ganze Zahl wahr ist.

Es sieht so aus, als ob die Kombination von next und itertools.ifilter Ihnen das gibt, was Sie wollen.

match = next(itertools.ifilter(None, (regex.match(big_text) for regex in regexes)))
A. Coady
quelle
Wenn die Antwort Null wäre, hätten wir ein Problem. Das next(iterator)war die Antwort, die mir fehlte.
cdleary
4

Haskell verwendet das, was Sie gerade beschrieben haben, als Funktion take(oder take 1technisch als Teilfunktion ). In Python Cookbook sind Generator-Wrapper geschrieben, die dieselbe Funktionalität wie ausführentake , takeWhileund dropin Haskell.

Aber warum das nicht eingebaut ist, ist Ihre Vermutung so gut wie meine.

Mark Rushakoff
quelle
3
Solche Funktionen ("eingebaute" in Bezug auf Typ und Geschwindigkeit!) Befinden sich im itertools-Modul der Standardbibliothek - genau wie (z. B.) reguläre Ausdrücke im re-Modul, mathematische Funktionen im mathematischen Modul usw. Immer schwer zu erreichen Entscheiden Sie, was im Hauptnamespace am besten dargestellt wird - Perl hat REs als integrierte Funktionen, Fortran hat SIN und COS & c, Haskell behält dort Namen wie take ... Python bevorzugt, alle diese Namensgruppen in Standardbibliotheksmodulen zu haben.
Alex Martelli
2
Wäre das Haskell-Äquivalent von first nicht head ? "take 1" gibt eine Liste zurück, kein Element.
tokland