Melhor maneira de lidar com list.index (pode não existir) em python?

113

Eu tenho um código parecido com este:

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

ok isso é simplificado, mas essa é a idéia. Agora thingpode não estar realmente na lista, caso em que desejo passar -1 como thing_index. Em outras linguagens, isso é o que você esperaria index()retornar se não conseguisse encontrar o elemento. Na verdade, ele lança um ValueError.

Eu poderia fazer isso:

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

Mas isso parece sujo, além disso, não sei se ValueErrorpoderia ser criado por algum outro motivo. Eu vim com a seguinte solução baseada em funções geradoras, mas parece um pouco complexa:

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

Existe uma maneira mais limpa de conseguir a mesma coisa? Vamos supor que a lista não esteja classificada.

Draemon
fonte
4
"... nesse caso eu quero passar -1 como thing_index." - Isso é definitivamente não-pitônico. Passar um valor de token (sem sentido) no caso de uma operação não ser bem-sucedida é desaprovado - as exceções realmente são o caminho certo aqui. Especialmente porque thing_list[-1]é uma expressão válida, significando a última entrada na lista.
Tim Pietzcker
@jellybean: facepalm ... identifique o codificador java: P
Draemon
4
@Tim: existe um str.findmétodo que faz exatamente isso: retorna -1quando a agulha não é encontrada no assunto.
SilentGhost
@Tim Nenhum seria melhor então ... e isso seria análogo a dict [chave] vs dict.get [chave]
Draemon
@SilentGhost: Hm, interessante. Talvez eu tenha que examinar isso com mais detalhes. str.index()lança uma exceção se a string de pesquisa não for encontrada.
Tim Pietzcker

Respostas:

66

Não há nada "sujo" em usar a cláusula try-except. Este é o caminho python. ValueErrorserá gerado pelo .indexmétodo apenas, porque é o único código que você tem lá!

Para responder ao comentário:
Em Python, mais fácil pedir perdão do que obter permissão, a filosofia está bem estabelecida, e não index não levantará esse tipo de erro para quaisquer outras questões. Não que eu possa pensar em algum.

SilentGhost
fonte
29
Certamente as exceções são para casos excepcionais, e dificilmente é isso. Eu não teria esse problema se a exceção fosse mais específica do que ValueError.
Draemon
1
Eu sei que só pode ser lançado a partir desse método, mas é garantido que só seja lançado por esse motivo ? Não que eu consiga pensar em outro motivo pelo qual o índice falharia ... mas não há exceções exatamente para aquelas coisas em que você pode não pensar?
Draemon
4
Não é {}.get(index, '')mais pitônico? Já para não falar mais curto mais legível.
Esteban Küber
1
Eu uso dict [key] quando espero que a chave exista e dict.get (key) quando não tenho certeza e estou procurando algo equivalente aqui. Retornar em Nonevez de -1 seria bom, mas como você mesmo comentou, str.find () retorna -1 então por que não deveria haver list.find () que faz a mesma coisa? Não estou
acreditando no
3
Mas o ponto é que a solução mais pitônica é usar apenas try / except e não o valor -1 sentinela. Ou seja, você deve reescrever otherfunction. Por outro lado, se não quebrou, ...
Andrew Jaffe
53
thing_index = thing_list.index(elem) if elem in thing_list else -1

Uma linha. Simples. Sem exceções.

Emil Ivanov
fonte
35
Simples sim, mas isso fará duas buscas lineares e, embora o desempenho não seja um problema em si, isso parece excessivo.
Draemon
4
@Draemon: Concordo - isso fará 2 passagens - mas é improvável que de uma base de código de mil linhas este seja o gargalo. :) Sempre é possível optar por uma solução imperativa com for.
Emil Ivanov
com lambdindexOf = lambda item,list_ : list_.index(item) if item in list_ else -1 # OR None
Alaa Akiel de
17

O dicttipo tem uma getfunção , onde se a chave não existir no dicionário, o segundo argumento para geté o valor que deve retornar. Da mesma forma, há setdefault, que retorna o valor em dictse a chave existir, caso contrário, ele define o valor de acordo com seu parâmetro padrão e retorna seu parâmetro padrão.

Você pode estender o listtipo para ter um getindexdefaultmétodo.

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

Que poderia ser usado como:

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

Não há nada de errado com o código que usa ValueError. Aqui está mais uma linha se você quiser evitar exceções:

thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)
jfs
fonte
Esse é o python 2.6? Eu sei que não mencionei isso, mas estou usando 2.5. Isso é provavelmente o que eu faria em 2.6
Draemon
1
@Draemon: Sim, a next()função existe no Python 2.6+. Mas é fácil de implementar para 2.5, consulte a implementação da função next () para Python 2.5
jfs
4

Esta questão é de filosofia da linguagem. Em Java, por exemplo, sempre houve uma tradição de que as exceções deveriam ser usadas apenas em "circunstâncias excepcionais", ou seja, quando os erros aconteceram, e não para controle de fluxo . No início, isso era por motivos de desempenho, pois as exceções do Java eram lentas, mas agora esse se tornou o estilo aceito.

Em contraste, o Python sempre usou exceções para indicar o fluxo normal do programa, como levantar um, ValueErrorconforme discutimos aqui. Não há nada de "sujo" nisso no estilo Python e há muito mais de onde isso veio. Um exemplo ainda mais comum é a StopIterationexceção gerada por um next()método de iterador para sinalizar que não há mais valores.

Tendayi Mawushe
fonte
Na verdade, o JDK joga maneira demasiadas exceções verificadas, então eu não tenho certeza de que a filosofia é realmente aplicado para Java. Eu não tenho um problema per se com StopIterationporque é claramente definido o que a exceção significa. ValueErroré um pouco genérico demais.
Draemon
Eu estava me referindo à ideia de que as exceções não devem ser usadas para controle de fluxo: c2.com/cgi/wiki?DontUseExceptionsForFlowControl , não tanto o número de exceções verificadas que o Java tem que toda uma outra discussão: mindview.net/Etc/Discussions / CheckedExceptions
Tendayi Mawushe
4

Se você faz isso com frequência, é melhor colocá-lo em uma função auxiliar:

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

Que tal isso 😃:

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
fonte
1

Que tal isso:

otherfunction(thing_collection, thing)

Em vez de expor algo tão dependente da implementação, como um índice de lista em uma interface de função, passe a coleção e a coisa e deixe a outra função lidar com os problemas de "teste de associação". Se outra função for escrita para ser agnóstica de tipo de coleção, então provavelmente começaria com:

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

que funcionará se thing_collection for uma lista, tupla, conjunto ou dicionário.

Isso é possivelmente mais claro do que:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:

que é o código que você já possui em outra função.

PaulMcG
fonte
1

Que tal assim:

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

Tenho o mesmo problema com o método ".index ()" nas listas. Não tenho nenhum problema com o fato de que ele lança uma exceção, mas discordo totalmente do fato de que é um ValueError não descritivo. Eu poderia entender se teria sido um IndexError, no entanto.

Posso ver por que retornar "-1" também seria um problema, porque é um índice válido em Python. Mas, realisticamente, nunca espero que um método ".index ()" retorne um número negativo.

Aqui vai uma linha (ok, é uma linha bastante longa ...), percorre a lista exatamente uma vez e retorna "Nenhum" se o item não for encontrado. Seria trivial reescrever para retornar -1, se você desejar.

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

Como usar:

>>> indexOf([1,2,3], 4)
>>>
>>> indexOf([1,2,3], 1)
0
>>>
Haavee
fonte
-2

Não sei por que você deve pensar que é sujo ... por causa da exceção? se você quiser um oneliner, aqui está:

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

mas eu desaconselho o uso; Acho que a solução de Ross Rogers é a melhor, use um objeto para encapsular seu comportamento desejado, não tente levar a linguagem ao seu limite ao custo da legibilidade.

Alan Franzoni
fonte
1
Sim, por causa da exceção. Seu código fará duas pesquisas lineares, não é? Não que o desempenho realmente importe aqui. A solução SuperDuperList é boa, mas parece um exagero nesta situação particular. Acho que vou acabar pegando apenas a exceção, mas queria ver se havia um jeito mais limpo (para minha estética).
Draemon
@Draemon: bem, você encapsulará o código que possui na find()função e estará tudo limpo;)
SilentGhost
1
É curioso que minha resposta tenha dois votos negativos, enquanto a de Emil Ivanov, embora semanticamente idêntica, é um dos mais votados. Provavelmente isso acontece porque o meu é mais lento, já que usei count () em vez do operador "in" ... pelo menos um comentário dizendo que teria sido ótimo :-)
Alan Franzoni
-2

Eu sugiro:

if thing in thing_list:
  list_index = -1
else:
  list_index = thing_list.index(thing)
Jonas
fonte
2
O problema com esta solução é que "-1" é um índice válido na lista (último índice; o primeiro do final). A melhor maneira de lidar com isso seria retornar False no primeiro branch de sua condição.
FanaticD