Python - 'se foo no dict' vs 'try: dict [foo]'

24

Isso é menos uma pergunta sobre a natureza da digitação de patos e mais sobre permanecer pitônico, suponho.

Primeiro de tudo - ao lidar com ditados, em particular quando a estrutura do ditado é razoavelmente previsível e uma determinada chave normalmente não está presente, mas às vezes existe, primeiro penso em duas abordagens:

if myKey in dict:
    do_some_work(dict[myKey])
else:
    pass

E, claro, Ye Olde 'perdão versus permissão'.

try:
    do_some_work(dict[myKey])
except KeyError:
    pass

Como um viajante Python, sinto que vejo o último preferido, o que só me parece estranho, porque os documentos do Python try/exceptsparecem preferidos quando há um erro real, em oposição a um ... hum ... ausência de sucesso?

Se o ditado ocasional não tiver chave no myDict e for sabido que nem sempre terá essa chave, é uma tentativa / exceto contextualmente enganosa? Este não é um erro de programação, é apenas um fato dos dados - esse ditado simplesmente não tinha essa chave específica.

Isso parece particularmente importante quando você olha para a sintaxe try / except / else, que parece ser realmente útil quando se trata de garantir que a tentativa não esteja capturando muitos erros. Você é capaz de fazer algo como:

try:
    foo += bar
except TypeError:
    pass
else:
    return some_more_work(foo)

Isso não levará a engolir todos os tipos de erros estranhos que provavelmente são resultado de algum código incorreto? O código acima pode estar impedindo você de ver que você está tentando adicionar 2 + {}e você pode nunca perceber que alguma parte do seu código está terrivelmente errada. Eu não sugiro que devemos verificar todos os tipos, é por isso que é Python e não JavaScript - mas, novamente, com o contexto de try / except, parece que ele deve pegar o programa fazendo algo que não deveria estar fazendo. de permitir que ele continue.

Sei que o exemplo acima é uma espécie de argumento de palha e é de fato intencionalmente ruim. Mas, dado o credo pitônico de better to ask forgiveness than permissionque não posso ajudar, sinto que ele sugere a questão de onde a linha na areia está entre a aplicação correta de if / else vs try / except, especialmente quando você sabe o que esperar os dados com os quais você está trabalhando.

Não estou nem falando de preocupações com velocidade ou práticas recomendadas aqui, estou meio que confuso com o diagrama de Venn percebido de casos em que parece que poderia ser de qualquer maneira, mas as pessoas erram ao lado de uma tentativa / exceto porque 'alguém em algum lugar disse que era pitônico'. Eu tirei conclusões erradas sobre a aplicação desta sintaxe?


fonte
Concordo que a tendência para tentar, exceto corre o risco de ocultar erros. Minha opinião é que você geralmente deve evitar a tentativa, exceto pelas condições que espera ver (é claro que algumas exceções se aplicam a esta regra).
Gordon Bean

Respostas:

26

Use o método get :

some_dict.get(the_key, default_value)

... onde default_valueestá o valor devolvido se the_keynão está na some_dict. Se você omitir default_value, Noneserá retornado se a chave estiver faltando.

Em geral, no Python, as pessoas tendem a preferir try / exceto do que verificar algo primeiro - consulte a entrada EAFP no glossário . Observe que muitas funções de "teste para associação" usam exceções nos bastidores.

detly
fonte
Esse tipo de problema não é o mesmo? Se estamos tentando trabalhar com um dicte fazendo um trabalho com base no que foi encontrado, parece que if myDict.get(a_key) is not None: do_work(myDict[a_key])é mais prolixo e mais um exemplo de aparência implícita antes de saltar - além de ser um IMHO um pouco menos legível. Talvez eu não esteja entendendo dict.get(a_key, a_default)no contexto certo, mas parece que é um precursor para escrever 'não-mude-se-for-elses' ou valores mágicos. Eu gosto do que esse método faz, porém, analisarei mais profundamente.
11
@ Stick - você faz:, data = myDict.get(a_key, default)e do_workfará a coisa certa quando for dado default(por exemplo None) ou você faz if data: do_work(data). Apenas uma pesquisa é necessária em ambos os casos. (Pode ser que if data is not None: ..., em ambos os casos, ainda seja apenas uma pesquisa e, em seguida, sua própria lógica possa assumir o controle).
detly
11
Então eu decidi que dict.get () me salvou bastante no meu projeto atual. Ele reduziu minha contagem de linhas, limpou um monte de try/except/elselixo horrível e apenas melhorou em geral o IMHO a legibilidade do meu código. Aprendi uma lição valiosa que já ouvi antes, mas que consegui deixar de levar em consideração: "Se você sente que está escrevendo muito código, pare e observe os recursos do idioma". Obrigado!!
@ Stick - prazer em ouvi-lo :) Eu gosto desse conselho, eu nunca ouvi isso antes.
detly
11
Tecnicamente, qual é o mais rápido?
Olivier Pons
4

A chave ausente é uma regra ou uma exceção ?

De um ponto de vista pragmático, jogar / pegar é muito mais lento do que verificar se a chave está na tabela. Se falta uma chave é uma ocorrência comum - você deve usar a condição

Outra coisa a favor da condição é uma cláusula else - é muito mais fácil entender quando um dos blocos é executado, considere que mesmo a mesma classe de exceção pode ser lançada a partir de várias instruções no bloco try.

O código na cláusula exceto não deve fazer parte da falha normal (por exemplo, se a chave não estiver presente, adicione-a) - deve estar lidando com o erro.

Eugene
fonte
4
"De um ponto de vista pragmático, jogar / pegar é muito mais lento do que verificar se a chave está na tabela" - não, não está. Não necessariamente, pelo menos. A última vez que verifiquei, as implementações mais comuns do Python fazem a versão try/ catchmais rapidamente.
detly
2
Jogar / capturar em python não é tão importante em termos de desempenho, na medida em que é frequentemente usado para controle de fluxo em python. Além disso, em algumas situações, é possível modificar esse ditado entre o teste e o acesso.
whatsisname 24/01
@whatsisname - Pensei em acrescentar isso à minha resposta, mas TBH, se você tem riscos de corrida como esse, tem problemas muito maiores que EAFP vs LBYL: P
detly
1

No espírito de ser "pitônico" e, especificamente, digitar pato - a tentativa / exceção parece quase frágil para mim.

Tudo o que você realmente deseja é algo com acesso semelhante ao ditado, mas __getitem__pode ser substituído para fazer algo que não é o esperado. Por exemplo, usando defaultdictda biblioteca padrão:

In [1]: from collections import defaultdict

In [2]: foo = defaultdict(int)

In [3]: foo['a'] = 1

In [4]: foo['b']  # Someone did access checking with a try/except exactly as you have in the question
Out[4]: 0

In [5]: 'a' in foo
Out[5]: True

In [6]: 'b' in foo  # We never set it, but now it exists!
Out[6]: True

In [7]: 'c' in foo
Out[7]: False

Como o valor de retorno padrão é Falsy, a verificação de acesso assim funciona na maioria das vezes muito bem. Parece manter o código mais simples também, e foi por isso que meus colegas de trabalho e eu o fizemos por meses.

Infelizmente, alguns meses depois, essas chaves extras acabaram causando problemas e erros difíceis de rastrear, porque 0se tornaram um valor válido. E às vezes nós iria usar inpara verificar se uma chave existe, tornando o código de fazer coisas diferentes, quando deveria ter sido idêntico.

A razão pela qual sugiro que pareça quebrada é porque, no espírito de digitação de pato do Python, tudo o que você deseja é algo parecido com um defaultdictditado e se encaixa perfeitamente nesse requisito, como qualquer objeto que você possa criar que herda dict. Você não pode garantir que o chamador não alterará sua implementação posteriormente, portanto, você deve fazer a coisa com menos efeitos colaterais.

Use a primeira versão if myKey in mydict,.


Além disso, observe que isso é especificamente sobre o exemplo da pergunta. O credo python de "Melhor pedir perdão do que permissão" tem como objetivo garantir que você realmente aja corretamente quando não conseguir o que deseja. Por exemplo, verificar se um arquivo existe e depois tentar lê-lo - pelo menos três coisas em que consigo pensar podem dar errado:

  • Uma condição de corrida é que o arquivo possa ter sido excluído / movido / etc
  • Você não tem permissões de leitura no arquivo
  • O arquivo foi corrompido

O primeiro em que você pode tentar "pedir permissão", mas, por natureza das condições de uma corrida, nem sempre funciona; o segundo é muito fácil esquecer de verificar; e o terceiro não pode ser verificado sem tentar ler o arquivo. De qualquer forma, o que você faz após a falha quase certamente será o mesmo; portanto, neste exemplo, é melhor "pedir perdão" usando uma tentativa / exceção.

Izkata
fonte