Le meilleur moyen de gérer list.index (pourrait ne pas exister) en python?

113

J'ai du code qui ressemble à ceci:

thing_index = thing_list.index(thing)
otherfunction(thing_list, thing_index)

ok donc c'est simplifié mais vous voyez l'idée. Maintenant, il se thingpeut que ce ne soit pas réellement dans la liste, auquel cas je veux passer -1 comme thing_index. Dans d'autres langues, c'est ce que vous vous attendez index()à retourner s'il ne trouve pas l'élément. En fait, il jette un ValueError.

Je pourrais faire ceci:

try:
    thing_index = thing_list.index(thing)
except ValueError:
    thing_index = -1
otherfunction(thing_list, thing_index)

Mais cela semble sale, en plus je ne sais pas si cela ValueErrorpourrait être élevé pour une autre raison. J'ai proposé la solution suivante basée sur les fonctions du générateur, mais cela me semble un peu complexe:

thing_index = ( [(i for i in xrange(len(thing_list)) if thing_list[i]==thing)] or [-1] )[0]

Existe-t-il un moyen plus propre d'atteindre la même chose? Supposons que la liste n'est pas triée.

Draemon
la source
4
"... auquel cas je veux passer -1 comme thing_index." - C'est définitivement anti-pythonique. Passer une valeur de jeton (sans signification) au cas où une opération échouerait est mal vu - les exceptions sont vraiment la bonne façon ici. D'autant que thing_list[-1]c'est une expression valide, c'est-à-dire la dernière entrée de la liste.
Tim Pietzcker
@jellybean: facepalm ... repérez le codeur java: P
Draemon
4
@Tim: il existe une str.findméthode qui fait exactement cela: retourne -1lorsque l'aiguille n'est pas trouvée dans le sujet.
SilentGhost
@Tim Aucun ne serait mieux alors ... et ce serait analogue à dict [key] vs dict.get [key]
Draemon
@SilentGhost: Hm, intéressant. Je devrais peut-être examiner cela plus en détail. str.index()lève une exception si la chaîne de recherche est introuvable.
Tim Pietzcker

Réponses:

66

Il n'y a rien de «sale» à utiliser la clause try-except. C'est la voie pythonique. ValueErrorsera déclenché par la .indexméthode uniquement, car c'est le seul code que vous avez là!

Pour répondre au commentaire:
en Python, la philosophie plus facile de demander pardon que d'obtenir l'autorisation est bien établie, et non index ne soulèvera pas ce type d'erreur pour d'autres problèmes. Non pas que je puisse penser à aucun.

SilentGhost
la source
29
Les exceptions sont sûrement des cas exceptionnels, et ce n'est guère cela. Je n'aurais pas un tel problème si l'exception était plus spécifique que ValueError.
Draemon
1
Je sais qu'elle ne peut être lancée qu'à partir de cette méthode, mais est-elle garantie qu'elle ne sera lancée que pour cette raison ? Non pas que je puisse penser à une autre raison pour laquelle l'index échouerait ... mais alors n'y a-t-il pas des exceptions pour exactement ces choses auxquelles vous ne pensez peut-être pas?
Draemon
4
N'est-ce pas {}.get(index, '')plus pythonique? Sans parler de plus court plus lisible.
Esteban Küber
1
J'utilise dict [key] quand j'attends la clé d'exister et dict.get (clé) quand je ne suis pas sûr, et je suis à la recherche d'quelque chose d' équivalent ici. Renvoyer Noneau lieu de -1 serait bien, mais comme vous l'avez commenté vous-même, str.find () renvoie -1 alors pourquoi n'y aurait-il pas list.find () qui fasse la même chose? Je n'achète pas l'argument "pythonique"
Draemon
3
Mais le fait est que la solution la plus pythonique est de n'utiliser que try / except et pas du tout la valeur sentinelle -1. IE vous devriez réécrire otherfunction. En revanche, si ce n'est pas cassé, ...
Andrew Jaffe
53
thing_index = thing_list.index(elem) if elem in thing_list else -1

Une ligne. Facile. Aucune exception.

Emil Ivanov
la source
35
Simple oui, mais cela fera deux recherches linéaires et bien que les performances ne soient pas un problème en soi, cela semble excessif.
Draemon
4
@Draemon: D'accord - cela fera 2 passes - mais il est peu probable qu'à partir d'une base de code de mille lignes, celle-ci soit le goulot d'étranglement. :) On peut toujours opter pour une solution impérative avec for.
Emil Ivanov
avec lambdindexOf = lambda item,list_ : list_.index(item) if item in list_ else -1 # OR None
Alaa Akiel
17

Le dicttype a une getfonction , où si la clé n'existe pas dans le dictionnaire, le 2ème argument de getest la valeur qu'il doit renvoyer. De même, il y a setdefault, qui renvoie la valeur dans dictsi la clé existe, sinon il définit la valeur en fonction de votre paramètre par défaut, puis retourne votre paramètre par défaut.

Vous pouvez étendre le listtype pour avoir une getindexdefaultméthode.

class SuperDuperList(list):
    def getindexdefault(self, elem, default):
        try:
            thing_index = self.index(elem)
            return thing_index
        except ValueError:
            return default

Qui pourrait alors être utilisé comme:

mylist = SuperDuperList([0,1,2])
index = mylist.getindexdefault( 'asdf', -1 )
Ross Rogers
la source
6

Il n'y a rien de mal avec votre code qui utilise ValueError. Voici encore un autre one-liner si vous souhaitez éviter les exceptions:

thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)
jfs
la source
Est-ce que c'est Python 2.6? Je sais que je ne l'ai pas mentionné, mais j'utilise 2.5. C'est probablement ce que je ferais en 2.6
Draemon
1
@Draemon: Oui, la next()fonction existe dans Python 2.6+. Mais il est facile à implémenter pour 2.5, voir l'implémentation de la fonction next () pour Python 2.5
jfs
4

Ce problème relève de la philosophie du langage. En Java par exemple, il y a toujours eu une tradition selon laquelle les exceptions ne devraient vraiment être utilisées que dans des «circonstances exceptionnelles», c'est-à-dire lorsque des erreurs se sont produites, plutôt que pour le contrôle de flux . Au début, c'était pour des raisons de performances, car les exceptions Java étaient lentes, mais maintenant c'est devenu le style accepté.

En revanche, Python a toujours utilisé des exceptions pour indiquer le déroulement normal du programme, comme lever un ValueErrorcomme nous en discutons ici. Il n'y a rien de "sale" à ce sujet dans le style Python et il y en a beaucoup d'autres d'où cela vient. Un exemple encore plus courant est l' StopIterationexception qui est déclenchée par la next()méthode d' un itérateur pour signaler qu'il n'y a pas d'autres valeurs.

Tendayi Mawushe
la source
En fait, le JDK lance beaucoup trop d'exceptions vérifiées, donc je ne suis pas sûr que la philosophie soit réellement appliquée à Java. Je n'ai pas de problème en soi, StopIterationcar il est clairement défini ce que signifie l'exception. ValueErrorest juste un peu trop générique.
Draemon
Je faisais référence à l'idée que les exceptions ne devraient pas être utilisées pour le contrôle de flux: c2.com/cgi/wiki?DontUseExceptionsForFlowControl , pas tant au nombre d'exceptions vérifiées que Java a qu'une toute autre discussion: mindview.net/Etc/Discussions / CheckedExceptions
Tendayi Mawushe
4

Si vous faites cela souvent, il est préférable de le réchauffer dans une fonction d'aide:

def index_of(val, in_list):
    try:
        return in_list.index(val)
    except ValueError:
        return -1 
Veneet Reddy
la source
4

Et ça 😃:

li = [1,2,3,4,5] # create list 

li = dict(zip(li,range(len(li)))) # convert List To Dict 
print( li ) # {1: 0, 2: 1, 3: 2, 4:3 , 5: 4}
li.get(20) # None 
li.get(1)  # 0 
Alaa Akiel
la source
1

Et ça:

otherfunction(thing_collection, thing)

Plutôt que d'exposer quelque chose d'aussi dépendant de l'implémentation comme un index de liste dans une interface de fonction, transmettez la collection et l'objet et laissez l'autre fonction gérer les problèmes de "test d'appartenance". Si une autre fonction est écrite pour être indépendante du type de collection, elle commencerait probablement par:

if thing in thing_collection:
    ... proceed with operation on thing

qui fonctionnera si thing_collection est une liste, un tuple, un ensemble ou un dict.

C'est peut-être plus clair que:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:

qui est le code que vous avez déjà dans une autre fonction.

PaulMcG
la source
1

Et comme ça:

temp_inx = (L + [x]).index(x) 
inx = temp_inx if temp_inx < len(L) else -1
Jie Xiong
la source
0

J'ai le même problème avec la méthode ".index ()" sur les listes. Je n'ai aucun problème avec le fait qu'il lève une exception mais je ne suis pas du tout d'accord avec le fait qu'il s'agit d'une ValueError non descriptive. Je pourrais comprendre si cela aurait été une IndexError, cependant.

Je peux voir pourquoi renvoyer "-1" serait également un problème car c'est un index valide en Python. Mais de façon réaliste, je ne m'attends jamais à ce qu'une méthode ".index ()" renvoie un nombre négatif.

Voici une ligne (ok, c'est une ligne assez longue ...), parcourt la liste exactement une fois et retourne "Aucun" si l'élément n'est pas trouvé. Il serait trivial de le réécrire pour renvoyer -1, si vous le désirez.

indexOf = lambda list, thing: \
            reduce(lambda acc, (idx, elem): \
                   idx if (acc is None) and elem == thing else acc, list, None)

Comment utiliser:

>>> indexOf([1,2,3], 4)
>>>
>>> indexOf([1,2,3], 1)
0
>>>
haavee
la source
-2

Je ne sais pas pourquoi vous devriez penser que c'est sale ... à cause de l'exception? si vous voulez un oneliner, le voici:

thing_index = thing_list.index(elem) if thing_list.count(elem) else -1

mais je déconseillerais de l'utiliser; Je pense que la solution de Ross Rogers est la meilleure, utilisez un objet pour encapsuler votre comportement souhaité, n'essayez pas de pousser le langage à ses limites au détriment de la lisibilité.

Alan Franzoni
la source
1
Oui, à cause de l'exception. Votre code fera deux recherches linéaires, n'est-ce pas? Ce n'est pas que la performance compte vraiment ici. La solution SuperDuperList est agréable, mais semble exagérée dans cette situation particulière. Je pense que je finirai par attraper l'exception, mais je voulais voir s'il y avait une manière plus propre (à mon esthétique).
Draemon
@Draemon: vous encapsulerez le code que vous avez dans la find()fonction et tout sera propre;)
SilentGhost
1
Il est curieux que ma réponse comporte deux votes négatifs, tandis que celle d'Emil Ivanov, bien que sémantiquement identique, est l'une des plus votées. Très probablement, cela se produit parce que le mien est plus lent, puisque j'ai utilisé count () au lieu de l'opérateur "in" ... au moins un commentaire disant que cela aurait été génial, cependant :-)
Alan Franzoni
-2

Je suggérerais:

if thing in thing_list:
  list_index = -1
else:
  list_index = thing_list.index(thing)
Jonas
la source
2
Le problème avec cette solution est que "-1" est un index valide dans la liste (dernier index; le premier à partir de la fin). La meilleure façon de gérer cela serait de retourner False dans la première branche de votre condition.
FanaticD