Как обрабатывать исключения в списках?

121

У меня есть понимание списка в Python, в котором каждая итерация может вызывать исключение.

Например , если у меня есть:

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

[1/egg for egg in eggs]

Я получу ZeroDivisionErrorисключение в 3-м элементе.

Как я могу обработать это исключение и продолжить выполнение понимания списка?

Единственный способ, который я могу придумать, - это использовать вспомогательную функцию:

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

Но мне это кажется немного громоздким.

Есть ли лучший способ сделать это в Python?

Примечание. Это простой пример (см. « Например » выше), который я придумал, потому что мой реальный пример требует некоторого контекста. Я не заинтересован в том, чтобы избежать ошибок деления на ноль, а в обработке исключений в понимании списка.

Натан Феллман
источник
4
Есть PEP 463, чтобы добавить выражение для обработки исключений. В вашем примере это было бы [1/egg except ZeroDivisionError: None for egg in (1,3,0,3,2)]. Но он все еще в черновом режиме. Я чувствую, что это не будет принято. Выражения Imho могут стать слишком запутанными (проверка нескольких исключений, наличие более сложных комбинаций (несколько логических операторов, сложные понимания и т. Д.)
ср.
1
Обратите внимание, что для этого конкретного примера вы можете использовать numpy ndarrayс соответствующими настройками в np.seterr. Это приведет к 1/0 = nan. Но я понимаю, что это не распространяется на другие ситуации, в которых возникает такая необходимость.
gerrit

Ответы:

97

В Python нет встроенного выражения, которое позволяет игнорировать исключение (или возвращать альтернативные значения & c в случае исключений), поэтому невозможно, буквально говоря, «обрабатывать исключения в понимании списка», потому что понимание списка является выражением содержащие другое выражение, ничего более (т. е. никаких операторов, и только операторы могут перехватывать / игнорировать / обрабатывать исключения).

Вызовы функций являются выражениями, а тела функций могут включать в себя все нужные вам операторы, поэтому делегирование оценки подверженного исключениям подвыражения функции, как вы заметили, является одним из возможных обходных путей (другие, когда это возможно, проверки значений, которые могут вызвать исключения, как это также предлагается в других ответах).

Правильные ответы на вопрос «как обрабатывать исключения в понимании списка» все выражают часть всей этой истины: 1) буквально, то есть лексически В самом понимании, вы не можете; 2) практически вы делегируете задание функции или проверяете значения, подверженные ошибкам, когда это возможно. Таким образом, ваше неоднократное утверждение о том, что это не ответ, является необоснованным.

Алекс Мартелли
источник
14
Понимаю. Итак, полный ответ будет заключаться в том, что я должен: 1. использовать функцию 2. не использовать понимание списка 3. пытаться предотвратить исключение, а не обрабатывать его.
Натан Феллман,
9
Я не вижу «не использовать понимание списков» как часть ответа на вопрос «как обрабатывать исключения в понимании списков», но я думаю, вы могли бы разумно рассматривать это как возможное последствие « лексически В LC, невозможно обрабатывать исключения ", что действительно является первой частью буквального ответа.
Алекс Мартелли,
Можете ли вы отловить ошибку в выражении генератора или в понимании генератора?
1
@AlexMartelli, будет ли предложение except так сложно работать в будущих версиях python? [x[1] for x in list except IndexError pass]. Не смог интерпретатор создать временную функцию, чтобы попробовать x[1]?
alancalvitti
@Nathan, 1,2,3 выше превращается в огромную головную боль в потоках функциональных данных, где 1. обычно требуется встроить функции через лямбды; 2. Альтернативой является использование множества вложенных циклов for, которые нарушают функциональную парадигму и приводят к ошибочному коду; 3. Часто ошибки представляют собой специальные и скрытые сложные наборы данных, которые, как латинское слово для обозначения данных, даны, поэтому их нелегко предотвратить.
alancalvitti
119

Я понимаю, что этот вопрос довольно старый, но вы также можете создать общую функцию, чтобы упростить такие вещи:

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

Затем, насколько вы понимаете:

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

Вы, конечно, можете сделать функцию дескриптора по умолчанию как хотите (скажем, вы бы предпочли по умолчанию вернуть «None»).

Надеюсь, это поможет вам или другим будущим зрителям этого вопроса!

Примечание: в python 3 я бы сделал только ключевое слово аргумента handle и поместил его в конец списка аргументов. Это сделало бы передачу аргументов и прочее через catch намного более естественной.

Брайан Хед
источник
2
чрезвычайно полезно, спасибо. Хотя я согласен с теоретическими комментариями, это показывает практический подход к решению проблемы, с которой я сталкивался неоднократно.
Пол
2
Отличный ответ. Один мод я предлагаю проходит argsи kwargsчерез к ручке , а также. Таким образом вы можете вернуть say eggвместо жестко запрограммированного 0или исключения, как вы это делаете.
Безумный физик
3
Вам также может потребоваться тип исключения в качестве необязательного аргумента (можно ли параметризовать типы исключений?), Чтобы неожиданные исключения выдавались вверх, а не игнорировали все исключения.
00prometheus
3
@Bryan, не могли бы вы предоставить код для "в python 3, я бы сделал только ключевое слово аргумента" handle "и поместил бы его в конец списка аргументов". попробовал разместить handleпосле **kwargи получил SyntaxError. Вы имеете в виду разыменование как kwargs.get('handle',e)?
alancalvitti
21

Ты можешь использовать

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

это просто пропустит нулевые элементы.

Питер
источник
28
Это не отвечает на вопрос, как обрабатывать исключения в понимании списка.
Натан Феллман,
8
ммм, да, это так. это избавляет от необходимости обрабатывать исключения. да, это не всегда правильное решение, но это обычное решение.
Питер
3
Я понимаю. Я забираю комментарий (хотя и не буду удалять его, поскольку это короткое «обсуждение» улучшает ответ).
Натан Феллман,
11

Нет лучшего способа. Во многих случаях вы можете использовать избегание, как это делает Питер.

Другой вариант - не использовать понимания

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

Вам решать, будет ли это более громоздко или нет.

Джон Ла Рой
источник
1
Как мне здесь использовать понимание?
Натан Феллман,
@ Натан: ты бы не стал. gnibbler говорит: Нет лучшего способа
SilentGhost
Извини ... Я пропустил «нет» в его ответе :-)
Натан Феллман,
4

Я думаю, что вспомогательная функция, предложенная тем, кто задает начальный вопрос, а также Брайаном Хедом, хороша и совсем не громоздка. Одна строка магического кода, которая выполняет всю работу, не всегда возможна, поэтому вспомогательная функция - идеальное решение, если кто-то хочет избежать forциклов. Однако я бы изменил его на этот:

# 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

Выход будет такой [(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]. С этим ответом вы полностью контролируете, чтобы продолжить как хотите.

Elmex80s
источник
Ницца. Это очень похоже на Eitherтип в некоторых языках функционального программирования (например, Scala), где Eitherможет содержать значение одного или другого типа, но не обоих одновременно. Единственная разница в том, что в этих языках идиоматично помещать ошибку слева, а значение - справа. Вот статья с дополнительной информацией .
Alex Palmer
3

Я не видел ни одного ответа об этом. Но этот пример был бы одним из способов предотвратить возникновение исключения для известных случаев сбоя.

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


Output: [1, 0, None, 0, 0]
Slakker
источник
Разве это не то же самое, что и этот ответ? stackoverflow.com/a/1528244/1084
Натан
Есть небольшая разница. Фильтрация применяется к выходу, а не к входному списку. Как вы можете видеть в опубликованном примере, я обозначил «Нет» для случая, который может вызвать исключение.
Slakker