Warum hat list keine sichere "get" -Methode wie das Wörterbuch?

264

Warum hat list keine sichere "get" -Methode wie das Wörterbuch?

>>> d = {'a':'b'}
>>> d['a']
'b'
>>> d['c']
KeyError: 'c'
>>> d.get('c', 'fail')
'fail'

>>> l = [1]
>>> l[10]
IndexError: list index out of range
CSZ
quelle
1
Listen werden für andere Zwecke als Wörterbücher verwendet. Das get () wird für typische Anwendungsfälle der Liste nicht benötigt. Für das Wörterbuch ist get () jedoch häufig nützlich.
Manager
42
Sie können jederzeit eine leere Unterliste aus einer Liste entfernen, ohne IndexError auszulösen, wenn Sie stattdessen nach einem Slice fragen: l[10:11]statt l[10]beispielsweise. () Die Unterliste hat das gewünschte Element, falls vorhanden)
jsbueno
56
Im Gegensatz zu einigen hier unterstütze ich die Idee eines Safes .get. Es wäre das Äquivalent zu l[i] if i < len(l) else default, aber lesbarer, prägnanter und würde es ermöglichen i, ein Ausdruck zu sein, ohne ihn neu berechnen zu müssen
Paul Draper,
6
Heute wünschte ich mir, das gäbe es. Ich verwende eine teure Funktion, die eine Liste zurückgibt, aber ich wollte nur das erste Element oder Nonewenn eines nicht existiert. Es wäre schön zu sagen gewesen, x = expensive().get(0, None)damit ich die nutzlose Rendite von teuer nicht in eine temporäre Variable stecken müsste.
Ryan Hiebert
2
@ Ryan meine Antwort kann Ihnen helfen stackoverflow.com/a/23003811/246265
Jake

Antworten:

112

Letztendlich hat es wahrscheinlich keine sichere .getMethode, da a dicteine assoziative Sammlung ist (Werte sind mit Namen verknüpft), bei der es ineffizient ist, zu überprüfen, ob ein Schlüssel vorhanden ist (und seinen Wert zurückzugeben), ohne eine Ausnahme auszulösen, während dies sehr trivial ist um Ausnahmen beim Zugriff auf Listenelemente zu vermeiden (da die lenMethode sehr schnell ist). Mit dieser .getMethode können Sie den mit einem Namen verknüpften Wert abfragen und nicht direkt auf das 37. Element im Wörterbuch zugreifen (was eher dem entspricht, was Sie von Ihrer Liste verlangen).

Natürlich können Sie dies einfach selbst implementieren:

def safe_list_get (l, idx, default):
  try:
    return l[idx]
  except IndexError:
    return default

Sie könnten es sogar monkeypatch auf den __builtins__.listKonstruktor in __main__, aber das wäre eine weniger weit verbreitete Änderung, da die meisten Codes es nicht verwenden. Wenn Sie dies nur mit Listen verwenden möchten, die mit Ihrem eigenen Code erstellt wurden, können Sie listdie getMethode einfach unterordnen und hinzufügen .

Nick Bastin
quelle
24
Python erlaubt nicht Monkeypatching eingebaute Typen wielist
Imran
7
@CSZ: Behebt .getein Problem, das Listen nicht haben - eine effiziente Möglichkeit, Ausnahmen beim Abrufen von Daten zu vermeiden, die möglicherweise nicht vorhanden sind. Es ist sehr trivial und sehr effizient zu wissen, was ein gültiger Listenindex ist, aber es gibt keine besonders gute Möglichkeit, dies für Schlüsselwerte in einem Wörterbuch zu tun.
Nick Bastin
10
Ich denke, hier geht es überhaupt nicht um Effizienz - prüfen, ob ein Schlüssel in einem Wörterbuch vorhanden ist und / oder einen Artikel zurückgeben O(1). In rohen Begriffen ist es nicht ganz so schnell wie das Überprüfen len, aber aus Sicht der Komplexität sind sie alle O(1). Die richtige Antwort ist die typische Verwendung / Semantik ...
Mark Longair
3
@Mark: Nicht alle O (1) sind gleich. Auch dictist nur Best-Case O (1), nicht alle Fälle.
Nick Bastin
4
Ich denke, dass die Leute hier den Punkt verfehlen. In der Diskussion sollte es nicht um Effizienz gehen. Bitte hören Sie mit der vorzeitigen Optimierung auf. Wenn Ihr Programm zu langsam ist, missbrauchen .get()Sie es oder Sie haben Probleme an anderer Stelle in Ihrem Code (oder Ihrer Umgebung). Der Sinn einer solchen Methode ist die Lesbarkeit des Codes. Die "Vanille" -Technik erfordert vier Codezeilen an jeder Stelle, an der dies durchgeführt werden muss. Die .get()Technik erfordert nur eine und kann leicht mit nachfolgenden Methodenaufrufen (z my_list.get(2, '').uppercase(). B. ) verkettet werden .
Tyler Crompton
66

Dies funktioniert, wenn Sie das erste Element möchten, wie z my_list.get(0)

>>> my_list = [1,2,3]
>>> next(iter(my_list), 'fail')
1
>>> my_list = []
>>> next(iter(my_list), 'fail')
'fail'

Ich weiß, es ist nicht genau das, wonach Sie gefragt haben, aber es könnte anderen helfen.

Jake
quelle
7
weniger pythonisch als funktionale Programmierung
Eric
next(iter(my_list[index:index+1]), 'fail')Ermöglicht jeden Index, nicht nur 0. Oder weniger FP, aber wohl mehr Pythonic und mit ziemlicher Sicherheit besser lesbar : my_list[index] if index < len(my_list) else 'fail'.
Alphabetasoup
47

Wahrscheinlich, weil es für die Listensemantik einfach nicht viel Sinn machte. Sie können jedoch ganz einfach Ihre eigenen erstellen, indem Sie Unterklassen erstellen.

class safelist(list):
    def get(self, index, default=None):
        try:
            return self.__getitem__(index)
        except IndexError:
            return default

def _test():
    l = safelist(range(10))
    print l.get(20, "oops")

if __name__ == "__main__":
    _test()
Keith
quelle
5
Dies ist bei weitem die pythonischste Antwort auf das OP. Beachten Sie, dass Sie auch eine Unterliste extrahieren können, was in Python eine sichere Operation ist. Mit mylist = [1, 2, 3] können Sie versuchen, das 9. Element mit mylist [8: 9] zu extrahieren, ohne eine Ausnahme auszulösen. Sie können dann testen, ob die Liste leer ist, und, falls sie nicht leer ist, das einzelne Element aus der zurückgegebenen Liste extrahieren.
Jose.angel.jimenez
1
Dies sollte die akzeptierte Antwort sein, nicht die anderen nicht-pythonischen Einzeiler-Hacks, insbesondere weil dadurch die Symmetrie mit Wörterbüchern erhalten bleibt.
Eric
1
Es ist nicht pythonisch, eigene Listen zu unterordnen, nur weil Sie eine nette getMethode benötigen . Lesbarkeit zählt. Und die Lesbarkeit leidet mit jeder zusätzlichen unnötigen Klasse. Verwenden Sie einfach den try / exceptAnsatz, ohne Unterklassen zu erstellen.
Jeyekomon
@Jeyekomon Es ist vollkommen pythonisch, die Boilerplate durch Unterklassen zu reduzieren.
Keith
42

Anstatt .get zu verwenden, sollte eine solche Verwendung für Listen in Ordnung sein. Nur ein Nutzungsunterschied.

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'
DU
quelle
15
Dies schlägt fehl, wenn wir versuchen, das neueste Element mit -1 zu erhalten.
Pretobomba
Beachten Sie, dass dies für zirkulär verknüpfte Listenobjekte nicht funktioniert. Zusätzlich bewirkt die Syntax, was ich gerne als "Scanblock" bezeichne. Wenn ich Code durchsuche, um zu sehen, was er tut, ist dies eine Zeile, die mich für einen Moment verlangsamen würde.
Tyler Crompton
Inline, wenn / else nicht mit älteren Python wie 2.6 funktioniert (oder ist es 2.5?)
Eric
3
@ TylerCrompton: In Python gibt es keine zirkulär verknüpfte Liste. Wenn Sie selbst eine geschrieben haben, können Sie eine .getMethode implementieren (außer ich bin mir nicht sicher, wie Sie erklären würden, was der Index in diesem Fall bedeutet oder warum er jemals fehlschlagen würde).
Nick Bastin
Eine Alternative, die negative Indizes außerhalb der Grenzen behandelt, wärelst[i] if -len(lst) <= i < len(l) else 'fail'
mic
17

Versuche dies:

>>> i = 3
>>> a = [1, 2, 3, 4]
>>> next(iter(a[i:]), 'fail')
4
>>> next(iter(a[i + 1:]), 'fail')
'fail'
Wsewolod Kulaga
quelle
1
Ich mag dieses, obwohl es die Erstellung einer neuen Unterliste zuerst erfordert.
Rick unterstützt Monica
15

Dank an jose.angel.jimenez


Für die "Oneliner" -Fans ...


Wenn Sie das erste Element einer Liste möchten oder wenn Sie einen Standardwert möchten, wenn die Liste leer ist, versuchen Sie:

liste = ['a', 'b', 'c']
value = (liste[0:1] or ('default',))[0]
print(value)

kehrt zurück a

und

liste = []
value = (liste[0:1] or ('default',))[0]
print(value)

kehrt zurück default


Beispiele für andere Elemente…

liste = ['a', 'b', 'c']
print(liste[0:1])  # returns ['a']
print(liste[1:2])  # returns ['b']
print(liste[2:3])  # returns ['c']

Mit Standard-Fallback…

liste = ['a', 'b', 'c']
print((liste[0:1] or ('default',))[0])  # returns a
print((liste[1:2] or ('default',))[0])  # returns b
print((liste[2:3] or ('default',))[0])  # returns c

Getestet mit Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)

qräbnö
quelle
1
Kurze Alternative : value, = liste[:1] or ('default',). Es sieht so aus, als ob Sie die Klammern brauchen.
qräbnö
13

Das Beste, was Sie tun können, ist, die Liste in ein Diktat umzuwandeln und dann mit der get-Methode darauf zuzugreifen:

>>> my_list = ['a', 'b', 'c', 'd', 'e']
>>> my_dict = dict(enumerate(my_list))
>>> print my_dict
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
>>> my_dict.get(2)
'c'
>>> my_dict.get(10, 'N/A')
fabelhaft
quelle
20
Eine vernünftige Problemumgehung, aber kaum "das Beste, was Sie tun können".
Tripleee
3
Sehr ineffizient. Hinweis: Anstelle dieses zip range lenDings könnte man auch verwendendict(enumerate(my_list))
Marian
3
Dies ist nicht das Beste, es ist das Schlimmste, was Sie tun können.
Erikbwork
3
Es ist das Schlimmste, wenn Sie die Leistung in Betracht ziehen. Wenn Sie sich für die Leistung interessieren, codieren Sie nicht in einer interpretierten Sprache wie Python. Ich finde diese Lösung mit einem Wörterbuch ziemlich elegant, mächtig und pythonisch. Frühe Optimierungen sind sowieso böse, also lasst uns ein Diktat haben und später sehen, dass es ein Engpass ist.
Eric
7

Also habe ich etwas mehr darüber recherchiert und es stellt sich heraus, dass es dafür nichts Spezielles gibt. Ich war aufgeregt, als ich list.index (value) gefunden habe. Es gibt den Index eines angegebenen Elements zurück, aber es gibt nichts, um den Wert an einem bestimmten Index abzurufen. Wenn Sie also nicht die Lösung safe_list_get verwenden möchten, die ich für ziemlich gut halte. Hier sind einige 1-Zeilen-if-Anweisungen, mit denen die Aufgabe je nach Szenario für Sie erledigt werden kann:

>>> x = [1, 2, 3]
>>> el = x[4] if len(x) > 4 else 'No'
>>> el
'No'

Sie können auch None anstelle von 'No' verwenden, was sinnvoller ist:

>>> x = [1, 2, 3]
>>> i = 2
>>> el_i = x[i] if len(x) == i+1 else None

Auch wenn Sie nur das erste oder letzte Element in der Liste erhalten möchten, funktioniert dies

end_el = x[-1] if x else None

Sie können diese auch zu Funktionen machen, aber mir hat die IndexError-Ausnahmelösung immer noch gefallen. Ich habe mit einer heruntergekommenen Version der safe_list_getLösung experimentiert und sie etwas einfacher gemacht (keine Standardeinstellung):

def list_get(l, i):
    try:
        return l[i]
    except IndexError:
        return None

Ich habe kein Benchmarking durchgeführt, um zu sehen, was am schnellsten ist.

radtek
quelle
1
Nicht wirklich pythonisch.
Eric
@ Eric welches Snippet? Ich denke, der Versuch, außer macht am meisten Sinn, wenn man ihn noch einmal betrachtet.
Radtek
Eine eigenständige Funktion ist nicht pythonisch. Ausnahmen sind zwar etwas pythonischer, aber nicht viel, da dies in Programmiersprachen so häufig vorkommt. Was viel pythonischer ist, ist ein neues Objekt, das den eingebauten Typ listdurch Unterklassen erweitert. Auf diese Weise kann der Konstruktor eine listoder alles nehmen, was sich wie eine Liste verhält, und die neue Instanz verhält sich wie eine list. Siehe Keith's Antwort unten, die meiner Meinung nach die akzeptierte sein sollte.
Eric
1
@Eric Ich habe die Frage nicht als OOP-spezifisch analysiert, sondern als "Warum haben Listen keine Analogie, dict.get()um einen Standardwert aus einer Listenindexreferenz zurückzugeben, anstatt abfangen zu müssen IndexError? Es geht also wirklich um Sprach- / Bibliotheksfunktionen (und nicht um OOP." vs. FP-Kontext). Außerdem muss man Ihre Verwendung von 'pythonic' wahrscheinlich als WWGD (da seine Verachtung für FP Python bekannt ist) qualifizieren und nicht unbedingt nur PEP8 / 20 erfüllen.
cowbert
1
el = x[4] if len(x) == 4 else 'No'- meinen Sie len(x) > 4? x[4]ist außerhalb der Grenzen, wenn len(x) == 4.
Mikrofon
4

Wörterbücher dienen zum Nachschlagen. Es ist sinnvoll zu fragen, ob ein Eintrag vorhanden ist oder nicht. Listen werden normalerweise iteriert. Es ist nicht üblich zu fragen, ob L [10] existiert, sondern ob die Länge von L 11 beträgt.

Warteschlange für Lehrlinge
quelle
Ja, stimme dir zu. Aber ich habe gerade die relative URL der Seite "/ group / Page_name" analysiert. Teilen Sie es durch '/' und wollten überprüfen, ob der Seitenname einer bestimmten Seite entspricht. Es wäre bequem, etwas wie [url.split ('/'). Get_from_index (2, None) == "lalala"] zu schreiben, anstatt eine zusätzliche Überprüfung auf Länge durchzuführen oder eine Ausnahme abzufangen oder eine eigene Funktion zu schreiben. Wahrscheinlich haben Sie Recht, es wird einfach als ungewöhnlich angesehen. Trotzdem bin ich immer noch nicht einverstanden =)
CSZ
@ Nick Bastin: Nichts falsch. Es geht um Einfachheit und Geschwindigkeit der Codierung.
CSZ
Es wäre auch nützlich, wenn Sie Listen als platzsparenderes Wörterbuch verwenden möchten, wenn die Schlüssel aufeinanderfolgende Ints sind. Natürlich verhindert das Vorhandensein einer negativen Indizierung dies bereits.
Antimon
-1

Ihr Anwendungsfall ist grundsätzlich nur relevant, wenn Sie Arrays und Matrizen mit fester Länge erstellen, damit Sie wissen, wie lange sie vor uns liegen. In diesem Fall erstellen Sie sie normalerweise auch, bevor Sie sie von Hand mit None oder 0 füllen, sodass tatsächlich bereits ein Index vorhanden ist, den Sie verwenden.

Man könnte das sagen: Ich brauche ziemlich oft .get () für Wörterbücher. Nach zehn Jahren als Vollzeitprogrammierer glaube ich nicht, dass ich es jemals auf einer Liste gebraucht habe. :) :)

Lennart Regebro
quelle
Wie wäre es mit meinem Beispiel in Kommentaren? Was ist einfacher und lesbarer? (url.split ('/'). getFromIndex (2) == "lalala") ODER (result = url.split (); len (result)> 2 und result [2] == "lalala"). Und ja, ich weiß, dass ich eine solche Funktion selbst schreiben kann =), aber ich war überrascht, dass eine solche Funktion nicht eingebaut ist.
CSZ
1
Ich würde sagen, in deinem Fall machst du es falsch. Die URL-Behandlung sollte entweder über Routen (Mustervergleich) oder Objektdurchquerung erfolgen. Um Ihren speziellen Fall zu beantworten : 'lalala' in url.split('/')[2:]. Das Problem bei Ihrer Lösung hier ist jedoch, dass Sie nur das zweite Element betrachten. Was ist, wenn die URL "/ monkeybonkey / lalala" lautet? Sie erhalten eine True, obwohl die URL ungültig ist.
Lennart Regebro
Ich habe nur das zweite Element genommen, weil ich nur das zweite Element brauchte. Aber ja, Scheiben scheinen eine gute Alternative zu sein
CSZ
@CSZ: Aber dann wird das erste Element ignoriert und in diesem Fall können Sie es überspringen. :) Sehen Sie, was ich meine, das Beispiel funktioniert im wirklichen Leben nicht so gut.
Lennart Regebro