如何处理列表推导中的异常?


120

我在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表达式可能变得过于混乱(检查多个异常,具有更复杂的组合(多个逻辑运算符,复杂的理解等))
cfi

1
请注意,对于此特定示例,您可以在中使用ndarray具有适当设置的numpy np.seterr。这将导致1/0 = nan。但是我意识到这并不能推广到其他需要这种情况的情况。
gerrit

Answers:


96

Python中没有内置表达式可让您忽略异常(或在异常的情况下返回替代值&c),因此从字面上来讲,“处理列表推导中的异常”是不可能的,因为列表推导是一个表达式包含其他表达式,仅此而已(即,没有语句,只有语句可以捕获/忽略/处理异常)。

函数调用是表达式,函数主体可以包含您想要的所有语句,因此,如您所注意到的,将易于发生异常的子表达式的评估委托给函数是一种可行的解决方法(其他可行时,检查可能引发异常的值,如其他答案中所建议)。

对“如何处理列表理解中的异常”这一问题的正确回答都表达了所有这些事实的一部分:1)从字面上,即从词法上讲,在理解本身中,你不能做到;2)实际上,在可行的情况下,您将作业委派给某个函数或检查易于出错的值。您一再声称这不是一个答案是没有根据的。


14
我懂了。因此,一个完整的答案应该是:1.使用函数2.不使用列表理解3.尝试防止异常而不是处理异常。
Nathan Fellman

9
我没有看到“不使用列表推导”作为“如何处理列表推导中的异常”的答案的一部分,但是我想您可以合理地将其视为“ 在LC中词法分析”的可能结果,不可能处理异常”,这的确是字面上答案的第一部分。
Alex Martelli

您可以在生成器表达式或生成器理解中捕获错误吗?

1
@ AlexMartelli,except子句在将来的python版本中会很难工作吗?[x[1] for x in list except IndexError pass]。解释器无法创建临时函数来尝试x[1]吗?
alancalvitti

@Nathan,上面的1,2,3变成了功能数据流中的头疼问题,其中1.人们通常希望通过lambda内联函数;2.替代方法是使用大量嵌套的for循环,这些循环违反功能范式并导致易于出错的代码;3.错误通常是临时的和潜在的复杂数据集,作为数据手段的拉丁文单词已给定,因此很难避免。
alancalvitti

118

我意识到这个问题已经很老了,但是您也可以创建一个通用函数来简化这种事情:

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'参数作为关键字,并将其放在参数列表的末尾。这将使实际传递的论点变得更加自然。


2
非常有用,谢谢。尽管我同意理论上的评论,但这显示了一种解决我反复遇到的问题的实用方法。
保罗

2
很好的答案。我建议的一种模式也是通过argskwargs处理。这样,您可以返回say egg而不是hardcoded 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

没有,没有更好的方法。在很多情况下,您可以像Peter一样使用回避

您的另一选择是不使用理解

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

抱歉...我错过了他回答中的'not':-)
Nathan Fellman

4

我认为,正如提出最初问题的人和布莱恩·海德(Bryan Head)所建议的那样,辅助功能很好,一点也不麻烦。单行执行所有工作的魔术代码总是不可能的,因此,如果要避免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

输出将是this [(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]。有了这个答案,您完全可以控制以任何您想要的方式继续。


真好 这看起来与Either某些函数式编程语言(例如Scala)中的类型非常相似,其中an Either可以包含一种或另一种类型的值,但不能同时包含两种类型。唯一的区别是,在这些语言中,习惯用法是将错误放在左侧,而将值放在右侧。 这是一篇包含更多信息的文章
亚历克斯·帕尔默

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.