Por que a lista não possui um método "get" seguro como o dicionário?


264

Por que a lista não possui um método "get" seguro como o dicionário?

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

1
As listas são usadas para propósitos diferentes dos dicionários. O get () não é necessário para casos de uso típicos da lista. No entanto, para o dicionário, o get () geralmente é útil.
Mgronber

42
Você sempre pode obter uma sublist vazia de uma lista sem gerar IndexError se solicitar uma fatia: em l[10:11]vez de l[10], por exemplo. () Th sublista terá o elemento desejado, se existir)
jsbueno

56
Ao contrário de alguns aqui, apoio a ideia de um cofre .get. Seria o equivalente a l[i] if i < len(l) else default, mas mais legível, mais concisa, e permitindo ia ser uma expressão sem ter que recalcular-lo
Paul Draper

6
Hoje eu desejei que isso existisse. Uso uma função cara que retorna uma lista, mas queria apenas o primeiro item, ou Nonese não existisse. Teria sido bom dizer x = expensive().get(0, None)para não ter que colocar o retorno inútil do caro em uma variável temporária.
Ryan Hiebert

2
@ Ryan minha resposta pode ajudá-lo a stackoverflow.com/a/23003811/246265
Jake

Respostas:


112

Por fim, provavelmente não possui um .getmétodo seguro , porque a dicté uma coleção associativa (os valores estão associados aos nomes), onde é ineficiente verificar se uma chave está presente (e retornar seu valor) sem gerar uma exceção, embora seja super trivial para evitar exceções ao acessar os elementos da lista (como o lenmétodo é muito rápido). O .getmétodo permite que você consulte o valor associado a um nome, não acesse diretamente o 37º item no dicionário (que seria mais parecido com o que você está pedindo na sua lista).

Obviamente, você pode implementar isso sozinho:

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

Você pode até monkeipá-lo no __builtins__.listconstrutor __main__, mas isso seria uma mudança menos difundida, já que a maioria dos códigos não o usa. Se você quiser apenas usá-lo com listas criadas por seu próprio código, basta subclassificar liste adicionar o getmétodo.


24
Python não permite tipos embutidos de monkeypatching comolist
Imran

7
@ CSZ: .getresolve um problema que as listas não têm - uma maneira eficiente de evitar exceções ao obter dados que podem não existir. É super trivial e muito eficiente saber o que é um índice de lista válido, mas não há uma maneira particularmente boa de fazer isso para valores-chave em um dicionário.
Nick Bastin

10
Eu não acho que isso seja sobre eficiência - verificar se uma chave está presente em um dicionário e / ou retornar um item O(1). Não será tão rápido em termos brutos quanto a verificação len, mas do ponto de vista da complexidade, são todos O(1). A resposta certa é o uso típico / semântica um ...
Mark Longair

3
@ Mark: Nem todos os O (1) são criados iguais. Além disso, dicté apenas o melhor caso O (1), nem todos os casos.
Nick Bastin

4
Eu acho que as pessoas estão perdendo o objetivo aqui. A discussão não deve ser sobre eficiência. Por favor, pare com a otimização prematura. Se o seu programa estiver muito lento, você está abusando .get()ou tem problemas em outras partes do seu código (ou ambiente). O objetivo de usar esse método é a legibilidade do código. A técnica "vanilla" requer quatro linhas de código em todos os lugares em que isso precisa ser feito. A .get()técnica requer apenas um e pode ser facilmente encadeada com chamadas de método subsequentes (por exemplo my_list.get(2, '').uppercase()).
Tyler Crompton

67

Isso funciona se você quiser o primeiro elemento, como my_list.get(0)

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

Eu sei que não é exatamente o que você pediu, mas pode ajudar os outros.


7
menos pythônico do que funcional de programação-esque
Eric

next(iter(my_list[index:index+1]), 'fail')Permite a qualquer índice, não apenas 0. Ou menos FP, mas sem dúvida mais Pythonic, e quase certamente mais legível: my_list[index] if index < len(my_list) else 'fail'.
alphabetasoup

47

Provavelmente porque simplesmente não fazia muito sentido para a semântica da lista. No entanto, você pode facilmente criar seus próprios subclasses.

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()

5
Essa é, de longe, a resposta mais pitônica do OP. Observe que você também pode extrair uma sub-lista, que é uma operação segura no Python. Dada mylist = [1, 2, 3], você pode tentar extrair o nono elemento com mylist [8: 9] sem disparar uma exceção. Você pode testar se a lista está vazia e, caso não esteja vazia, extrair o elemento único da lista retornada.
Jose.angel.jimenez

1
Essa deve ser a resposta aceita, não os outros hacks não-pitônicos de uma linha, especialmente porque conserva a simetria com os dicionários.
Eric

1
Não há nada de pitoniano em subclassificar suas próprias listas apenas porque você precisa de um bom getmétodo. Legibilidade conta. E a legibilidade sofre com cada classe adicional desnecessária. Basta usar a try / exceptabordagem sem criar subclasses.
Jeyekomon 02/10/19

@Jeyekomon É perfeitamente Pythonic reduzir o clichê por subclasse.
Keith

42

Em vez de usar .get, usar desta forma deve estar ok para listas. Apenas uma diferença de uso.

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'

15
Isso falhará se tentarmos obter o elemento mais recente com -1.
Pretobomba

Observe que isso não funciona para objetos de lista vinculados circularmente. Além disso, a sintaxe faz com que eu chamo de "bloco de verificação". Ao digitalizar o código para ver o que ele faz, essa é uma linha que me atrasaria por um momento.
Tyler Crompton

linha if / else não funciona com python mais velhos como 2.6 (ou é 2,5?)
Eric

3
@TylerCrompton: Não existe uma lista vinculada circularmente em python. Se você escreveu um por conta própria, está livre para implementar um .getmétodo (exceto que não tenho certeza de como explicaria o que o índice significava nesse caso, ou por que um dia iria falhar).
precisa

Uma alternativa que lida com índices negativos fora dos limites serialst[i] if -len(lst) <= i < len(l) else 'fail'
mic

17

Tente o seguinte:

>>> i = 3
>>> a = [1, 2, 3, 4]
>>> next(iter(a[i:]), 'fail')
4
>>> next(iter(a[i + 1:]), 'fail')
'fail'

1
Eu gosto deste, embora exija a criação de uma nova sub-lista primeiro.
Rick apoia Monica

15

Créditos a jose.angel.jimenez


Para os fãs "oneliner" ...


Se você deseja o primeiro elemento de uma lista ou se deseja um valor padrão, se a lista estiver vazia, tente:

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

retorna a

e

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

retorna default


Exemplos para outros elementos…

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

Com fallback padrão…

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

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


1
Alternativa curto: value, = liste[:1] or ('default',). Parece que você precisa dos parênteses.
qräbnö 31/03

13

A melhor coisa que você pode fazer é converter a lista em um dict e acessá-la com o método 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')

20
Uma solução razoável, mas dificilmente "a melhor coisa que você pode fazer".
tripleee

3
Muito ineficiente. Nota: Em vez de que zip range lencoisa, pode-se usar apenasdict(enumerate(my_list))
Marian

3
Esta não é a melhor coisa, é a pior coisa que você poderia fazer.
Erikbwork 29/10/16

3
É a pior coisa se você considerar o desempenho ... se você se importa com o desempenho, não codifica em uma linguagem interpretada como python. Acho essa solução usando um dicionário bastante elegante, poderoso e pitônico. As otimizações iniciais são más de qualquer maneira, então vamos dar um ditado e ver mais tarde que é um gargalo.
Eric

7

Então, eu fiz mais algumas pesquisas sobre isso e verifica-se que não há nada específico para isso. Fiquei empolgado quando encontrei list.index (value), ele retorna o índice de um item especificado, mas não há nada para obter o valor em um índice específico. Portanto, se você não quiser usar a solução safe_list_get, que eu acho muito boa. Aqui estão algumas instruções liner if que podem fazer o trabalho para você, dependendo do cenário:

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

Você também pode usar Nenhum em vez de 'Não', o que faz mais sentido:

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

Além disso, se você deseja apenas obter o primeiro ou o último item da lista, isso funciona

end_el = x[-1] if x else None

Você também pode transformá-las em funções, mas eu ainda gostei da solução de exceção IndexError. Eu experimentei uma versão dummied da safe_list_getsolução e a tornei um pouco mais simples (sem padrão):

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

Não foram comparados para ver o que é mais rápido.


1
Não é realmente pitônico.
Eric

@ Eric qual snippet? Eu acho que a tentativa, exceto faz mais sentido, olhando novamente.
Radtek

Uma função autônoma não é pitônica. As exceções são um pouco mais pitônicas, mas não muito, pois é um padrão tão comum nas linguagens de programação. O que é muito mais pítônico é um novo objeto que estende o tipo interno listao subclassificá-lo. Dessa forma, o construtor pode usar uma listou qualquer coisa que se comporte como uma lista, e a nova instância se comporta como uma list. Veja a resposta de Keith abaixo, que deve ser o IMHO aceito.
Eric

1
@ Eric Eu analisei a questão não como específica de OOP, mas como "por que as listas não têm analogia para dict.get()retornar um valor padrão de uma referência de índice de lista em vez de ter que pegar IndexError? Então, realmente é sobre o recurso de idioma / biblioteca (e não OOP x contexto FP) Além disso, provavelmente é necessário qualificar o uso de 'pythonic' como talvez WWGD (como é conhecido o seu desprezo pelo FP Python) e não necessariamente apenas satisfazendo o PEP8 / 20.
cowbert

1
el = x[4] if len(x) == 4 else 'No'- você quer dizer len(x) > 4? x[4]está fora dos limites se len(x) == 4.
mic

4

Os dicionários são para pesquisas. Faz sentido perguntar se uma entrada existe ou não. As listas geralmente são iteradas. Não é comum perguntar se L [10] existe, mas se o comprimento de L é 11.


Sim, concordo com você. Mas acabei de analisar o URL relativo da página "/ group / Page_name". Divida-o por '/' e quis verificar se PageName é igual a determinada página. Seria confortável escrever algo como [url.split ('/'). Get_from_index (2, None) == "lalala"] em vez de fazer uma verificação extra de comprimento ou capturar uma exceção ou gravar uma função própria. Provavelmente você está certo, é simplesmente considerado incomum. De qualquer forma eu ainda não concordar com esta =)
CSZ

@ Nick Bastin: Nada de errado. É tudo uma questão de simplicidade e velocidade de codificação.
CSZ 26/02

Também seria útil se você quisesse usar as listas como um dicionário com maior eficiência de espaço nos casos em que as chaves são ints consecutivas. Claro que a existência de indexação negativa já impede isso.
Antimony

-1

O seu caso de uso é basicamente relevante apenas para a criação de matrizes e matrizes de comprimento fixo, para que você saiba quanto tempo demora antes. Nesse caso, você normalmente também os cria antes de preenchê-los com Nenhum ou 0, para que, de fato, já exista qualquer índice que você usar.

Você poderia dizer o seguinte: preciso de .get () nos dicionários com bastante frequência. Depois de dez anos como programador em tempo integral, acho que nunca precisei disso em uma lista. :)


E o meu exemplo nos comentários? O que é mais simples e legível? (url.split ('/'). getFromIndex (2) == "lalala") OR (resultado = url.split (); len (resultado)> 2 e resultado [2] == "lalala"). E sim, eu sei que posso escrever essa função pessoalmente =), mas fiquei surpreso que essa função não esteja embutida.
CSZ 26/02/11

1
Eu diria que no seu caso você está fazendo errado. O tratamento de URL deve ser feito por rotas (correspondência de padrões) ou passagem de objeto. Mas, para responder a seu caso específico: 'lalala' in url.split('/')[2:]. Mas o problema com sua solução aqui é que você olha apenas para o segundo elemento. E se o URL for '/ monkeybonkey / lalala'? Você receberá um, Truemesmo que o URL seja inválido.
Lennart Regebro

Eu peguei apenas o segundo elemento porque eu precisava apenas do segundo elemento. Mas sim, fatias parecem boa alternativa de trabalho
CSZ

@ CSZ: Mas então o primeiro elemento é ignorado e, nesse caso, você pode ignorá-lo. :) Veja o que quero dizer, o exemplo não funciona tão bem na vida real.
Lennart Regebro
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.