Pourquoi la liste n'a-t-elle pas de méthode «get» sûre comme le dictionnaire?

264

Pourquoi la liste n'a-t-elle pas une méthode sûre "get" comme le dictionnaire?

>>> 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
la source
1
Les listes sont utilisées à des fins différentes de celles des dictionnaires. Le get () n'est pas nécessaire pour les cas d'utilisation typiques de list. Cependant, pour le dictionnaire, get () est assez souvent utile.
mgronber
42
Vous pouvez toujours obtenir une sous-liste vide d'une liste sans augmenter IndexError si vous demandez une tranche à la place: l[10:11]au lieu de l[10], par exemple. () La sous-liste aura l'élément souhaité s'il existe)
jsbueno
56
Contrairement à certains ici, je soutiens l'idée d'un coffre-fort .get. Ce serait l'équivalent l[i] if i < len(l) else default, mais plus lisible, plus concis, et permettant id'être une expression sans avoir à la recalculer
Paul Draper
6
Aujourd'hui, je souhaitais que cela existe. J'utilise une fonction coûteuse qui renvoie une liste, mais je ne voulais que le premier élément, ou Nones'il n'en existait pas. Cela aurait été bien de dire x = expensive().get(0, None)que je n'aurais pas à mettre le retour inutile de cher dans une variable temporaire.
Ryan Hiebert
2
@Ryan ma réponse peut vous aider stackoverflow.com/a/23003811/246265
Jake

Réponses:

112

En fin de compte, il n'a probablement pas de .getméthode sûre car a dictest une collection associative (les valeurs sont associées aux noms) où il est inefficace de vérifier si une clé est présente (et de renvoyer sa valeur) sans lever d'exception, alors qu'elle est super triviale pour éviter les exceptions d'accès aux éléments de la liste (car la lenméthode est très rapide). La .getméthode vous permet d'interroger la valeur associée à un nom, et non d'accéder directement au 37e élément du dictionnaire (ce qui ressemblerait davantage à ce que vous demandez à votre liste).

Bien sûr, vous pouvez facilement l'implémenter vous-même:

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

Vous pouvez même le monkeypatch sur le __builtins__.listconstructeur dans __main__, mais ce serait un changement moins répandu car la plupart du code ne l'utilise pas. Si vous souhaitez simplement l'utiliser avec des listes créées par votre propre code, vous pouvez simplement sous list- classer et ajouter la getméthode.

Nick Bastin
la source
24
Python n'autorise pas les types intégrés de monkeypatch commelist
Imran
7
@CSZ: .getrésout un problème que les listes n'ont pas - un moyen efficace d'éviter les exceptions lors de l'obtention de données qui peuvent ne pas exister. Il est super trivial et très efficace de savoir ce qu'est un index de liste valide, mais il n'y a aucun moyen particulièrement bon de le faire pour les valeurs clés dans un dictionnaire.
Nick Bastin le
10
Je ne pense pas que ce soit une question d'efficacité - vérifier si une clé est présente dans un dictionnaire et / ou renvoyer un élément O(1). Ce ne sera pas aussi rapide en termes bruts que la vérification len, mais d'un point de vue complexité, ils sont tous O(1). La bonne réponse est celle typique d'utilisation / sémantique ...
Mark Longair
3
@Mark: Tous les O (1) ne sont pas créés égaux. En outre, dictseul le meilleur cas est O (1), pas tous les cas.
Nick Bastin
4
Je pense que les gens manquent le point ici. La discussion ne devrait pas porter sur l'efficacité. Veuillez arrêter avec l'optimisation prématurée. Si votre programme est trop lent, vous abusez .get()ou vous avez des problèmes ailleurs dans votre code (ou environnement). L'intérêt d'une telle méthode est la lisibilité du code. La technique "vanille" nécessite quatre lignes de code à chaque endroit que cela doit être fait. La .get()technique n'en nécessite qu'un et peut facilement être enchaînée avec des appels de méthode ultérieurs (par exemple my_list.get(2, '').uppercase()).
Tyler Crompton
68

Cela fonctionne si vous voulez le premier élément, comme my_list.get(0)

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

Je sais que ce n'est pas exactement ce que vous avez demandé, mais cela pourrait aider les autres.

Jake
la source
7
moins pythonique que la programmation-esque fonctionnelle
Eric
next(iter(my_list[index:index+1]), 'fail')Permet de tout indice, non seulement 0. Ou moins FP , mais sans doute plus Pythonic, et presque certainement plus facile à lire: my_list[index] if index < len(my_list) else 'fail'.
alphabetasoup
47

Probablement parce que cela n'avait tout simplement pas beaucoup de sens pour la sémantique des listes. Cependant, vous pouvez facilement créer le vôtre en sous-classant.

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
la source
5
C'est, de loin, la réponse la plus pythonique à l'OP. Notez que vous pouvez également extraire une sous-liste, ce qui est une opération sûre en Python. Étant donné mylist = [1, 2, 3], vous pouvez essayer d'extraire le 9ème élément avec mylist [8: 9] sans déclencher une exception. Vous pouvez ensuite tester si la liste est vide et, dans le cas où elle ne l'est pas, extraire l'élément unique de la liste renvoyée.
jose.angel.jimenez
1
Cela devrait être la réponse acceptée, pas les autres hacks à une ligne non pythoniques, surtout parce qu'il conserve la symétrie avec les dictionnaires.
Eric
1
Il n'y a rien de pythonique à sous-classer vos propres listes simplement parce que vous avez besoin d'une belle getméthode. La lisibilité compte. Et la lisibilité souffre avec chaque classe supplémentaire inutile. Utilisez simplement l' try / exceptapproche sans créer de sous-classes.
Jeyekomon
@Jeyekomon C'est parfaitement Pythonic de réduire le passe-partout en sous-classant.
Keith
42

Au lieu d'utiliser .get, une utilisation comme celle-ci devrait être correcte pour les listes. Juste une différence d'utilisation.

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'
TU
la source
15
Cela échoue si nous essayons d'obtenir le dernier élément avec -1.
pretobomba
Notez que cela ne fonctionne pas pour les objets de liste liés de manière circulaire. De plus, la syntaxe provoque ce que j'aime appeler un "bloc d'analyse". Lorsque vous parcourez le code pour voir ce qu'il fait, c'est une ligne qui me ralentirait un instant.
Tyler Crompton
en ligne si / sinon ne fonctionne pas avec un python plus ancien comme 2.6 (ou est-ce 2.5?)
Eric
3
@TylerCrompton: Il n'y a pas de liste liée de façon circulaire en python. Si vous en avez écrit une vous-même, vous êtes libre d'avoir implémenté une .getméthode (sauf que je ne sais pas comment vous expliqueriez ce que l'index signifiait dans ce cas, ni pourquoi il échouerait).
Nick Bastin
Une alternative qui gère les indices négatifs hors des limites seraitlst[i] if -len(lst) <= i < len(l) else 'fail'
mic
17

Essaye ça:

>>> i = 3
>>> a = [1, 2, 3, 4]
>>> next(iter(a[i:]), 'fail')
4
>>> next(iter(a[i + 1:]), 'fail')
'fail'
Vsevolod Kulaga
la source
1
j'aime celui-ci, bien qu'il nécessite d'abord la création d'une nouvelle sous-liste.
Rick soutient Monica
15

Crédits à jose.angel.jimenez


Pour les fans "oneliner"…


Si vous voulez le premier élément d'une liste ou si vous voulez une valeur par défaut si la liste est vide, essayez:

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

Retour a

et

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

Retour default


Exemples pour d'autres éléments…

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

Avec repli par défaut…

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

Testé avec Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)

qräbnö
la source
1
Alternative à court: value, = liste[:1] or ('default',). Il semble que vous ayez besoin des parenthèses.
qräbnö
14

La meilleure chose que vous puissiez faire est de convertir la liste en dict, puis d'y accéder avec la méthode get:

>>> 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')
fab
la source
20
Une solution de contournement raisonnable, mais à peine "la meilleure chose que vous puissiez faire".
tripleee
3
Très inefficace cependant. Remarque: Au lieu de cette zip range lenchose, on pourrait simplement utiliserdict(enumerate(my_list))
Marian
3
Ce n'est pas la meilleure chose, c'est la pire chose que vous puissiez faire.
erikbwork
3
C'est la pire chose si vous considérez les performances ... si vous vous souciez des performances, vous ne codez pas dans un langage interprété comme python. Je trouve cette solution en utilisant un dictionnaire plutôt élégant, puissant et pythonique. Les premières optimisations sont mauvaises de toute façon, alors faisons un dicton et voyons plus tard que c'est un goulot d'étranglement.
Eric
7

J'ai donc fait des recherches supplémentaires à ce sujet et il s'avère qu'il n'y a rien de spécifique à cela. Je me suis excité quand j'ai trouvé list.index (valeur), il renvoie l'index d'un élément spécifié, mais il n'y a rien pour obtenir la valeur à un index spécifique. Donc, si vous ne voulez pas utiliser la solution safe_list_get qui, je pense, est plutôt bonne. Voici quelques instructions qui peuvent faire le travail pour vous selon le scénario:

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

Vous pouvez également utiliser None au lieu de 'No', ce qui est plus logique .:

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

De plus, si vous souhaitez simplement obtenir le premier ou le dernier élément de la liste, cela fonctionne

end_el = x[-1] if x else None

Vous pouvez également en faire des fonctions, mais j'ai toujours aimé la solution d'exception IndexError. J'ai expérimenté avec une version factice de la safe_list_getsolution et je l'ai rendue un peu plus simple (pas de défaut):

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

Je n'ai pas de référence pour voir ce qui est le plus rapide.

radtek
la source
1
Pas vraiment pythonique.
Eric
@Eric quel extrait? Je pense que l'essai, sauf est plus logique en le regardant à nouveau.
radtek
Une fonction autonome n'est pas pythonique. Les exceptions sont en effet un peu plus pythoniques, mais pas beaucoup car c'est un modèle si commun dans les langages de programmation. De plus, pythonic est un nouvel objet qui étend le type intégré listen le sous- classant . De cette façon, le constructeur peut prendre un listou tout ce qui se comporte comme une liste, et la nouvelle instance se comporte comme un list. Voir la réponse de Keith ci-dessous qui devrait être celle acceptée à mon humble avis.
Eric
1
@Eric J'ai analysé la question non pas comme spécifique à la POO mais comme "pourquoi les listes n'ont-elles pas d'analogie pour dict.get()renvoyer une valeur par défaut à partir d'une référence d'index de liste au lieu d'avoir à intercepter IndexError? Il s'agit donc vraiment de la fonction de langue / bibliothèque (et non de POO Contexte FP) De plus, il faut probablement qualifier votre utilisation de 'pythonic' comme peut-être WWGD (comme son dédain pour FP Python est bien connu) et pas nécessairement satisfaisant juste PEP8 / 20.
cowbert
1
el = x[4] if len(x) == 4 else 'No'- tu veux dire len(x) > 4? x[4]est hors limites si len(x) == 4.
mic
4

Les dictionnaires sont destinés aux recherches. Il est logique de demander si une entrée existe ou non. Les listes sont généralement itérées. Il n'est pas courant de demander si L [10] existe mais plutôt si la longueur de L est 11.

File d'attente des apprentis
la source
Oui, d'accord avec toi. Mais je viens d'analyser l'url relative de la page "/ groupe / nom_de_édition". Divisez-le par '/' et vouliez vérifier si PageName est égal à certaine page. Il serait confortable d'écrire quelque chose comme [url.split ('/'). Get_from_index (2, None) == "lalala"] au lieu de faire une vérification supplémentaire de la longueur ou d'attraper une exception ou d'écrire sa propre fonction. Vous avez probablement raison, cela est tout simplement considéré comme inhabituel. Quoi qu'il en soit, je suis toujours en désaccord avec cela =)
CSZ
@Nick Bastin: Rien de mal. Il s'agit de simplicité et de rapidité de codage.
CSZ
Il serait également utile si vous vouliez utiliser des listes comme dictionnaire plus efficace dans les cas où les clés sont des entiers consécutifs. Bien sûr, l'existence d'une indexation négative arrête déjà cela.
Antimony
-1

Votre cas d'utilisation n'est fondamentalement pertinent que lorsque vous effectuez des tableaux et des matrices de longueur fixe, afin que vous sachiez combien de temps ils sont avant la main. Dans ce cas, vous les créez généralement avant de les remplir manuellement avec None ou 0, de sorte qu'en fait, tout index que vous utiliserez existe déjà.

On pourrait dire ceci: j'ai souvent besoin de .get () dans les dictionnaires. Après dix ans en tant que programmeur à temps plein, je ne pense pas en avoir jamais eu besoin sur une liste. :)

Lennart Regebro
la source
Que diriez-vous de mon exemple dans les commentaires? Quoi de plus simple et lisible? (url.split ('/'). getFromIndex (2) == "lalala") OR (result = url.split (); len (result)> 2 et result [2] == "lalala"). Et oui, je sais que je peux écrire une telle fonction moi-même =) mais j'ai été surpris qu'une telle fonction ne soit pas intégrée.
CSZ
1
Je dirais que dans votre cas, vous vous trompez. La gestion des URL doit être effectuée soit par des routes (correspondance de modèle) soit par une traversée d'objet. Mais, pour répondre à votre cas particulier: 'lalala' in url.split('/')[2:]. Mais le problème avec votre solution ici est que vous ne regardez que le deuxième élément. Que faire si l'URL est '/ monkeybonkey / lalala'? Vous obtiendrez un Truemême si l'URL n'est pas valide.
Lennart Regebro le
Je n'ai pris que le deuxième élément car je n'avais besoin que du deuxième élément. Mais oui, les tranches semblent être une bonne alternative de travail
CSZ
@CSZ: Mais alors le premier élément est ignoré, et dans ce cas, vous pouvez le sauter. :) Voyez ce que je veux dire, l'exemple ne fonctionne pas si bien dans la vraie vie.
Lennart Regebro le