Como lidar com exceções em uma lista de compreensão?


120

Eu tenho uma compreensão de lista em Python em que cada iteração pode lançar uma exceção.

Por exemplo , se eu tenho:

eggs = (1,3,0,3,2)

[1/egg for egg in eggs]

Vou obter uma ZeroDivisionErrorexceção no terceiro elemento.

Como posso lidar com essa exceção e continuar a execução da compreensão da lista?

A única maneira que consigo pensar é usar uma função auxiliar:

def spam(egg):
    try:
        return 1/egg
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

Mas isso parece um pouco complicado para mim.

Existe uma maneira melhor de fazer isso em Python?

Nota: Este é um exemplo simples (veja " por exemplo " acima) que inventei porque meu exemplo real requer algum contexto. Não estou interessado em evitar erros de divisão por zero, mas em lidar com exceções em uma compreensão de lista.


4
Existe um PEP 463 para adicionar uma expressão para lidar com exceções. No seu exemplo, seria [1/egg except ZeroDivisionError: None for egg in (1,3,0,3,2)]. Mas ainda está em modo de rascunho. Meu pressentimento é que isso não será aceito. Expressões Imho podem ficar muito confusas (verificando múltiplas exceções, tendo combinações mais complexas (múltiplos operadores lógicos, compreensões complexas, etc.)
cfi

1
Observe que, para este exemplo específico , você pode usar um numpy ndarraycom as configurações apropriadas em np.seterr. Isso resultaria em 1/0 = nan. Mas eu percebo que isso não generaliza para outras situações em que essa necessidade surge.
gerrit

Respostas:


96

Não há nenhuma expressão embutida no Python que permita ignorar uma exceção (ou retornar valores alternativos etc. no caso de exceções), então é impossível, literalmente falando, "lidar com exceções em uma compreensão de lista" porque uma compreensão de lista é uma expressão contendo outra expressão, nada mais (ou seja, nenhuma instrução e apenas as instruções podem capturar / ignorar / manipular exceções).

Chamadas de função são expressão e os corpos de função podem incluir todas as instruções que você deseja, portanto, delegar a avaliação da subexpressão sujeita a exceções a uma função, como você notou, é uma solução alternativa viável (outras, quando viável, são verificações de valores que podem provocar exceções, como também sugerido em outras respostas).

As respostas corretas à questão "como lidar com exceções em uma compreensão de lista" estão todas expressando parte de toda essa verdade: 1) literalmente, isto é, lexicamente NA compreensão em si, você não pode; 2) na prática, você delega o trabalho a uma função ou verifica se há valores sujeitos a erros quando isso for viável. Sua afirmação repetida de que esta não é uma resposta é, portanto, infundada.


14
Entendo. Portanto, uma resposta completa seria que eu deveria: 1. usar uma função 2. não usar a compreensão de lista 3. tentar evitar a exceção em vez de tratá-la.
Nathan Fellman

9
Não vejo "não usar compreensões de lista" como parte da resposta a "como lidar com exceções em compreensões de lista", mas acho que você poderia razoavelmente ver isso como uma possível consequência de " lexicamente no LC, não é possível lidar com exceções ", que é de fato a primeira parte da resposta literal.
Alex Martelli

Você pode detectar um erro em uma expressão do gerador ou compreensão do gerador?

1
@AlexMartelli, uma cláusula de exceção seria tão difícil de trabalhar em versões futuras do python? [x[1] for x in list except IndexError pass]. O intérprete não poderia criar uma função temporária para tentar x[1]?
alancalvitti

@Nathan, 1,2,3 acima se transformam em tremendas dores de cabeça em fluxos de dados funcionais, onde 1. normalmente se deseja incorporar funções por meio de lambdas; 2. A alternativa é usar muitos loops for aninhados que violam o paradigma funcional e levam a um código sujeito a erros; 3. Freqüentemente, os erros são conjuntos de dados complexos ad-hoc e latentes que são, como a palavra latina significa dados, dados, portanto não podem ser facilmente evitados.
alancalvitti

118

Sei que essa pergunta é bastante antiga, mas você também pode criar uma função geral para tornar esse tipo de coisa mais fácil:

def catch(func, handle=lambda e : e, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception as e:
        return handle(e)

Então, em sua compreensão:

eggs = (1,3,0,3,2)
[catch(lambda : 1/egg) for egg in eggs]
[1, 0, ('integer division or modulo by zero'), 0, 0]

É claro que você pode fazer com que o identificador padrão funcione como quiser (digamos que você prefira retornar 'Nenhum' por padrão).

Espero que isso ajude você ou qualquer futuro visualizador desta questão!

Nota: em Python 3, eu faria a palavra-chave do argumento 'lidar' apenas, e colocaria no final da lista de argumentos. Isso tornaria a passagem de argumentos e coisas do gênero muito mais naturais.


2
extremamente útil, obrigado. Embora eu concorde com os comentários teóricos, isso mostra uma abordagem prática para resolver um problema que tive repetidamente.
Paul

2
Boa resposta. Um mod que eu sugiro é de passagem argse kwargspara lidar também. Dessa forma, você pode retornar digamos em eggvez de um código fixo 0ou uma exceção como está fazendo.
Mad Physicist

3
Você também pode querer o tipo de exceção como um argumento opcional (os tipos de exceção podem ser parametrizados?), De forma que exceções inesperadas sejam lançadas para cima em vez de ignorar todas as exceções.
00prometheus de

3
@Bryan, você pode fornecer o código para "em python 3, eu faria apenas a palavra-chave do argumento 'manipular' e colocaria no final da lista de argumentos." tentei colocar handledepois **kwarge consegui um SyntaxError. Você quer desreferenciar como kwargs.get('handle',e)?
alancalvitti

21

Você pode usar

[1/egg for egg in eggs if egg != 0]

isso simplesmente pulará os elementos que são zero.


28
Isso não responde à questão de como lidar com exceções em uma compreensão de lista.
Nathan Fellman

8
umm, sim, faz. ele elimina a necessidade de lidar com exceções. sim, não é a solução certa o tempo todo, mas é comum.
Peter,

3
Compreendo. Retiro o comentário (embora não o exclua, pois essa breve 'discussão' melhora a resposta).
Nathan Fellman

11

Não, não há maneira melhor. Em muitos casos, você pode usar a evitação como Peter faz

Sua outra opção é não usar compreensões

eggs = (1,3,0,3,2)

result=[]
for egg in eggs:
    try:
        result.append(egg/0)
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

Cabe a você decidir se isso é mais complicado ou não


1
Como eu usaria as compreensões aqui?
Nathan Fellman

@Nathan: você não faria. gnibbler diz: Não, não há maneira melhor
SilentGhost

Desculpe ... Eu perdi o 'não' em sua resposta :-)
Nathan Fellman

4

Acho que uma função auxiliar, conforme sugerido por aquele que faz a pergunta inicial e por Bryan Head também, é boa e nem um pouco complicada. Uma única linha de código mágico que faz todo o trabalho nem sempre é possível, então uma função auxiliar é uma solução perfeita se alguém quiser evitar forloops. No entanto, eu iria modificá-lo para este:

# A modified version of the helper function by the Question starter 
def spam(egg):
    try:
        return 1/egg, None
    except ZeroDivisionError as err:
        # handle division by zero error        
        return None, err

A saída será esta [(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]. Com essa resposta, você tem controle total para continuar da maneira que quiser.


Agradável. Isso é muito semelhante ao Eithertipo em algumas linguagens de programação funcionais (como Scala), onde um Eitherpode conter um valor de um tipo ou outro, mas não ambos. A única diferença é que nesses idiomas é idiomático colocar o erro no lado esquerdo e o valor no lado direito. Aqui está um artigo com mais informações .
Alex Palmer,

3

Não vi nenhuma resposta mencionando isso. Mas este exemplo seria uma maneira de evitar que uma exceção seja levantada para casos de falha conhecidos.

eggs = (1,3,0,3,2)
[1/egg if egg > 0 else None for egg in eggs]


Output: [1, 0, None, 0, 0]

Não é o mesmo que esta resposta? stackoverflow.com/a/1528244/1084
Nathan Fellman

Existe uma diferença sutil. A filtragem é aplicada na saída e não na lista de entrada. Como você pode ver no exemplo postado, denotei "Nenhum" para o caso que pode causar uma exceção.
Slakker
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.