Pythonic的方法来结合`for`和`try`块

9
我正在解析一个JSON数据流的代码中。对于每个数组,我有以下代码:
for node in parse_me:
    # It's important that one iteration failing doesn't cause all iterations to fail.
    try:
        i = node['id'] # KeyError?
        function_that_needs_int (i) # TypeError?
        # possibly other stuff

    except Exception as e:
        LogErrorMessage ('blah blah blah {} in node {}'.fmt(e, node))

我不喜欢这会让我的for循环嵌套两层,仅仅是因为我需要防止异常中断循环。有没有一种方法可以使这段代码更加扁平化?


8
我认为那看起来不错,有什么问题吗? - jonrsharpe
3
如果嵌套的层次让你感到困扰,那就是时候将相关内容移到它们自己的函数中了。 - Mark Ransom
3
我需要停止异常中断循环。 - Kijewski
5
@MarounMaroun 看起来没有必要比较两个完全不同的代码片段的"性感程度"。 - EvenLisle
1
看起来很好,但是如果“# 对节点执行大量操作”很长,你应该绝对为此代码部分创建一个方法。然后你可以决定是否将 try catch 放在这个方法中。从我的角度来看,最好使用一个方法,在 for 循环中像你的例子一样加上 try catch。 - Alexandre Mazel
显示剩余7条评论
2个回答

5
那么,您应该做一些像这样的事情:
def iterate_safe(parse_me, message, action):
    for node in parse_me:
        try:
            action(node)
        except Exception as e:
            LogErrorMessage(message.fmt(e, node))

然后像这样调用

def action(node):
    do_whatever_must_be_done_with(node)

iterate_safe(parse_me, action, 'blah blah blah {} in node {}')
iterate_safe(parse_me, other_action, 'spam ham {} in node {}')

1

编辑:原问题似乎暗示整个解析操作都在一个巨大的for循环中;根据下面的评论,我的答案已经修改。

不要编写多个for循环,每个循环都必须包含一个try/catch块,而是编写描述必须在循环内部执行的函数,并编写一个装饰器应用于它们,该装饰器将包围每个函数与for-loop和try/catch日志记录逻辑。这有点像glglgl的解决方案,但在我看来更具Python风格。例如:

def apply_to_nodes_and_log_errs(node_visit_func):
    def safe_iterating_visitor(nodes_to_parse):
        for node in nodes_to_parse:
            try:
                node_visit_func(node)
            except StandardError as e:
                LogErrorMessage ('blah blah blah {} in node {}'.fmt(e, node))
    return safe_iterating_visitor

@apply_to_nodes_and_log_errs
def action_one(node):
    # ... "lots of stuff" :D

@apply_to_nodes_and_log_errs
def action_two(node):
    # different stuff

如果您更喜欢将装饰器拆分成块:
def iterate_over_nodelist(node_visit_func):
    def iterating_visitor(nodes_to_parse):
        for node in nodes_to_parse:
            node_visit_func(node)
    return iterating_visitor

def safely_visit_log_errs(node_visit_func):
    def safe_logging_visitor(node_to_visit):
        try:
            node_visit_func(node)
        except StandardError as e:
            LogErrorMessage ('blah blah blah {} in node {}'.fmt(e, node))
    return safe_logging_visitor

def apply_to_nodes_and_log_errs(node_visit_func):
    return iterate_over_nodelist(safely_visit_log_errs(node_visit_func))

# ... write visit functions

这可以进一步改进,使用functools.wraps
请注意,尽管如果您的标准是“尽可能使用少的缩进级别”,这可能看起来有点丑陋,但它实际上非常Pythonic;在编写装饰器时,确实无法避免相当多的缩进级别。
最后,请注意从ExceptionStandardError的更改,我仍然强烈建议。

我在问题中编辑了一段给我们带来困扰的代码示例。有时我们必须在 for 循环体内做很多工作,但有时只需要一两行。唯一的一致之处是这种 for 循环习惯用法总是出现。我的错误大多源于 JSON 源数据不良。有时数据缺失或我们得到古怪的值(一个应该是 int 类型的字段中出现 "12" 等),而且经常会遇到未经测试的情况。 - QuestionC
我在这个应用程序中看到的每一个异常都是“数据出错”的异常,所以我现在不担心传递异常。如果我开始遇到异常,那么重新抛出它们就有意义了,我会开始重新抛出它们。 - QuestionC
@QuestionC 所以,你实际上有多个循环,而不是一个大循环?同样,键盘中断异常是一个很好的例子;你根本不知道它们何时会发生(无论你对代码的经验如何),你几乎总是应该重新抛出它们。至少,捕获StandardErrorException更好。 - Kyle Strand
这是一个基于JSON语法的解析器。语法中的每个数组都使用类似于我上面发布的for: try:块进行解析。因此,我们有多少个循环就有多少个格式集合。它读起来非常好(代码基本上看起来像我们的业务需求),但由于我们必须处理异常而导致的双重嵌套循环令人讨厌。 - QuestionC
@QuestionC 我已经编辑了我的问题,以反映您正在使用多个循环。请注意,try不是一个“循环”,因此无论您如何实现它,您都不会真正双重嵌套循环。 - Kyle Strand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接