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.