将函数中的每个for循环改为在每次迭代失败后自动执行错误处理。

3
这个问题是基于 catch errors within generator and continue afterwards 的。

我有大约50个类似但不同的函数,它们尝试从网站中提取URL等信息。由于每个网站都不同,每个函数也不同,而且网站随着时间的推移而改变,所以这些代码很混乱,不能信任。

以下是一个简化的示例,或者查看第一个问题中的示例。

def _get_units(self):
    for list1 in self.get_list1():
        for list2 in self.get_list2(list1):
            for unit in list2:
                yield unit

我希望你能够使用这个函数来改变其行为,使其与以下内容匹配:
def _get_units(self):
    for list1 in self.get_list1():
        try:                 
            for list2 in self.get_list2(list1):
                try:
                    for unit in list2:
                        try:
                            yield unit
                        except Exception as e:
                            log_exception(e)
                except Exception as e:
                    log_exception(e)
        except Exception as e:
            log_exception(e)

简而言之,我想将这个转变为:
for x in list:
    do_stuff(x)

转换为:

for x in list:
    try:
        do_stuff(x)
    except Exception as e:
        log_exception(e)

我希望在我的函数中为每个for循环添加错误处理,但我想以Pythonic的方式实现。我不想在需要修改的50个函数中到处散布try:except块。这是否可能?如果是,最DRY的方法是什么,是否可以在一个地方处理所有的错误呢?

更新:此问题原来包含continue语句和日志记录,但正如mgilson指出的那样,这是不必要的。

更新2:根据georgesl的答案,该函数变为以下内容:

from contextlib import contextmanager

@contextmanager
def ErrorManaged():
    try:
        yield
    except Exception as e:
        log_exception(e)


def _get_units(self):
    for list1 in self.get_list1():
        with ErrorManaged():              
            for list2 in self.get_list2(list1):
                with ErrorManaged():
                    for unit in list2:
                        with ErrorManaged():
                            yield unit

这确实更干净。不过,仅仅使用装饰器可能会更好。有人能告诉我这是否可能吗?如果不行,我会接受georgesl的答案。


你内部的try-except是多余的——yield不会引发异常。 - Steven Rumbalski
continue 在异常处理中的目的是什么?也许我漏掉了什么,但它似乎是不必要的。(请注意,如果您可以删除 continue,这个问题将变得容易得多。) - mgilson
由于第二个函数不会被编写而是计算,我让 try:except 存在,因为它将存在于循环的内容无论如何。 - ToonAlfrink
@mgilson 想想看...我怎么没注意到?如果我用try:except整个块,我就不需要continue了...正在编辑... - ToonAlfrink
是否可以将一些错误处理移动到get_list1get_list2中? - Steven Rumbalski
不,它们只是为了简化而存在的,它们甚至不存在。如果确实存在,那么它们只是同一文件中的辅助函数,而不是居中的函数。 - ToonAlfrink
3个回答

3

您可能想要使用装饰器,或者更好的方式是上下文管理器

from contextlib import contextmanager


def HandleError(func):

    def wrapped(*args, **kwargs):

        try:
            func(*args, **kwargs)
        except Exception:
            print "Bug on node #", args[0]


    return wrapped

@contextmanager
def ErrorManaged():
    try:
        yield
    except Exception:
        print "Oh noes, the loop crashed"



@HandleError
def do_something(x):
    print x
    if x==5:
        raise('Boom !')




with ErrorManaged():
    for x in range(10):
        do_something(x)
        if x == 7 :
            raise('aaaah !')

不是我不知道谷歌,但你能否包含一个contextmanager文档的链接? - ToonAlfrink
我也考虑过上下文管理器,但你仍然需要每次重复 with ... 部分,而不是装饰函数使其自动化。 - mgilson
你是对的@mgilson,也许我可以使用生成器来节省一些空间,但我没有成功让它工作。 - lucasg
这看起来不错,但是很抱歉,我还不熟悉 with 语句。我尝试过实现它,但是我真的不太理解。你能否在我的函数中实现你的答案,这样我就可以看到并测试一下? - ToonAlfrink

2

我可能会“装饰”函数本身。假设你遵循DRY原则并将它们存储在列表或其他东西中:

def decorate_function(func):
    def decorated(x):
        try:
            return func(x)
        except Exception as e:
            log_error(e)
    return decorated

现在你只需要使用这个代码修饰你的函数,它就可以记录下你的错误。请注意,这假设上面的 continue 语句是不必要的。按照我的理解,它看起来并没有真正被使用,但我可能有所遗漏。
如果这些函数确实没有返回任何东西,那么你可以根据是否遇到异常返回 TrueFalse。你可以使用它来编写你的 continue 逻辑。类似这样:
if not decorated_function(x): continue

@StevenRumbalski -- 但是上面的代码片段并不会这样做,对吧?每个for都包含一个单独的try-except。所以如果你捕获了一个错误,你会进入except,然后continue只会继续当前的循环。 - mgilson
请注意,第二个代码片段是我想要第一个代码片段表现出来的方式,而不是现有的代码。 - ToonAlfrink
@ToonAlfrink -- 我理解了。但我看不出你的两个例子之间有什么关系。(其中一个版本是使用self...并在其中yield一个值,而另一个版本则是实际调用一个函数。) - mgilson

0

我再仔细考虑了一下,唯一适合我的需求的解决方案似乎是修改代码本身。所以我开始动手:

from contextlib import contextmanager
import inspect

@contextmanager
def ErrorManaged():
    try:
        yield
    except Exception as e:
        print e



def get_units():
    for x in range(-5,5):
        print(x)

        if x % 3 == 0:
            raise Exception("x nope")

        for y in range(-5,5):
            print("\t{}".format(y))

            if y % 3 == 0:
            raise Exception("y nope")

            for z in range(-5,5):
                print("\t\t{}".format(z))

                if z % 3 == 0:
                    raise Exception("z nope")


import re

def modify_get_units(get_units):    
    lines = inspect.getsourcelines(get_units)[0]
    add = "with ErrorManaged():\n"
    new = []
    tabsize = 0
    for c in lines[1]:
        if c == " ":
            tabsize += 1
        else:
            break

    count = 0
    for line in lines:
        new.append(" " * tabsize * count + line)
        m = re.match(r"^(\s+)for\s[()\w,]+\sin\s[^ :\n]+:\n$",line)
        if m:
            count += 1
            new.append(m.group(1) + " " * tabsize * count + add)

    return "".join(new)

oldfunc = inspect.getsource(get_units)
newfunc = modify_get_units(get_units)

#printing function bodies to show results

print(oldfunc)
print("\n\n\n")
print(newfunc)


#re-declare get_units
exec newfunc

#execute, but now now
#get_units()

输出:

toon@ToonAlfrinkPC ~ $ python test.py
def get_units():
    for x in range(-5,5):
        print(x)

        if x % 3 == 0:
            raise Exception("x nope")

        for y in range(-5,5):
            print("\t{}".format(y))

            if y % 3 == 0:
                raise Exception("y nope")

            for z in range(-5,5):
                print("\t\t{}".format(z))

                if z % 3 == 0:
                    raise Exception("z nope")





def get_units():
    for x in range(-5,5):
        with ErrorManaged():
            print(x)

            if x % 3 == 0:
                raise Exception("x nope")

            for y in range(-5,5):
                with ErrorManaged():
                    print("\t{}".format(y))

                    if y % 3 == 0:
                        raise Exception("y nope")

                    for z in range(-5,5):
                        with ErrorManaged():
                            print("\t\t{}".format(z))

                            if z % 3 == 0:
                                raise Exception("z nope")

感谢您帮助我到达那里!


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