Python 在捕获异常后如何返回到 try 块?

1 投票
2 回答
661 浏览
提问于 2025-04-17 18:15

我有一些装饰器(Decorators)用于跟踪某个递归函数。我想知道如何返回到try块。我尝试过使用while循环,但对我来说不管用,因为我的函数是递归的。有没有人能给我个主意,怎么处理这个问题?

问题是,当函数change_t抛出异常时,我想继续执行我的try块。

这是我的装饰器和函数:

正确的结果:

,- change_t([9, 7, 5], 44)
| ,- change_t([9, 7, 5], 35)
| | ,- change_t([9, 7, 5], 26)
| | | ,- change_t([9, 7, 5], 17)
| | | | ,- change_t([9, 7, 5], 8)
| | | | | ,- change_t([7, 5], 8)
| | | | | | ,- change_t([7, 5], 1)
| | | | | | | ,- change_t([5], 1)
| | | | | | | | ,- change_t([], 1)
| | | | | | ,- change_t([5], 8)
| | | | | | | ,- change_t([5], 3)
| | | | | | | | ,- change_t([], 3)
| | | | | | | ,- change_t([], 8)
| | | | ,- change_t([7, 5], 17)
| | | | | ,- change_t([7, 5], 10)
| | | | | | ,- change_t([7, 5], 3)
| | | | | | | ,- change_t([5], 3)
| | | | | | | | ,- change_t([], 3)
| | | | | | ,- change_t([5], 10)
| | | | | | | ,- change_t([5], 5)
| | | | | | | | ,- change_t([5], 0)
| | | | | | | | `- []
| | | | | | | `- [5]
| | | | | | `- [5, 5]
| | | | | `- [5, 5]
| | | | `- [7, 5, 5]
| | | `- [7, 5, 5]
| | `- [9, 7, 5, 5]
| `- [9, 9, 7, 5, 5]
`- [9, 9, 9, 7, 5, 5]

这是我得到的结果:它在我预期会出现异常的地方停止了。

change_t([9, 7, 5], 44)
,- change_t ([9, 7, 5], 44)
| ,- change_t ([9, 7, 5], 35)
| | ,- change_t ([9, 7, 5], 26)
| | | ,- change_t ([9, 7, 5], 17)
| | | | ,- change_t ([9, 7, 5], 8)
| | | | | ,- change_t ([7, 5], 8)
| | | | | | ,- change_t ([7, 5], 1)
| | | | | | | ,- change_t ([5], 1)
| | | | | | | | ,- change_t ([], 1)
| | | | | | `- 1
| | | | | `- 1
| | | | `- 1
| `- 8
`- 8
`- 17
`- 26
`- 35
`- 44
44

2 个回答

0

当程序出现错误(我们称之为异常)时,控制权就会从出错的代码中转移出去。这个控制权会交给第一个能够处理这个错误的地方,或者直接交给程序的主循环。

看看你的代码:

if a==0:
    return []
elif len(l)==0:
    raise ChangeException()
elif l[0]>a:
    return change_t(l[1:],a)

假设第三个条件成立,也就是 l[0]>a,而且列表 l 的长度是 1,这时会发生什么呢?在下一次调用时,也就是 return change_t(l[1:],a),程序会抛出一个异常,而这个异常除了主循环之外没有人能处理:这就是你代码出错的原因。你需要把这个第三个条件放在一个尝试捕获的结构里,这样可以根据你想要实现的目标来处理异常。

2

正如我在评论中提到的,你需要重新抛出异常,这样原来的函数才能捕捉到这个异常并继续执行。你只需要在异常处理的部分减少一个缩进,这样就不会缩得太靠左了:

class traced(object):
    indent =0
    def __init__(self,f):
        self.__name__=f.__name__
        self.indent=0
        self.f=f         
    def __call__(self,*args,**kwargs):
        string=""           
        if kwargs:
           l=[]
           for (key, value) in kwargs.items():
               l.append(str(key) + "=" + str(value))
           a=', '.join(l)
           string = '('+a+')'              
        else:
             l=[]
             for value in args:
                 l.append(str(value))
             a=', '.join(l)
             string = '('+a+')'       
        print('| ' * traced.indent + ',- '+ self.__name__+' '+string)   
        try:
            traced.indent+=1
            value = self.f(*args,**kwargs)                
        except Exception:
            traced.indent-=1  # <-- only decrement by one
            raise             # <-- reraise the exception so the original function can catch it
        traced.indent-=1
        print('| '* traced.indent + "`- "+ repr(value))          
        return value

这样就可以正常工作了:

>>> change_t([9, 7, 5], 44)
,- change_t ([9, 7, 5], 44)
| ,- change_t ([9, 7, 5], 35)
| | ,- change_t ([9, 7, 5], 26)
| | | ,- change_t ([9, 7, 5], 17)
| | | | ,- change_t ([9, 7, 5], 8)
| | | | | ,- change_t ([7, 5], 8)
| | | | | | ,- change_t ([7, 5], 1)
| | | | | | | ,- change_t ([5], 1)
| | | | | | | | ,- change_t ([], 1)
| | | | | | ,- change_t ([5], 8)
| | | | | | | ,- change_t ([5], 3)
| | | | | | | | ,- change_t ([], 3)
| | | | | | | ,- change_t ([], 8)
| | | | ,- change_t ([7, 5], 17)
| | | | | ,- change_t ([7, 5], 10)
| | | | | | ,- change_t ([7, 5], 3)
| | | | | | | ,- change_t ([5], 3)
| | | | | | | | ,- change_t ([], 3)
| | | | | | ,- change_t ([5], 10)
| | | | | | | ,- change_t ([5], 5)
| | | | | | | | ,- change_t ([5], 0)
| | | | | | | | `- []
| | | | | | | `- [5]
| | | | | | `- [5, 5]
| | | | | `- [5, 5]
| | | | `- [7, 5, 5]
| | | `- [7, 5, 5]
| | `- [9, 7, 5, 5]
| `- [9, 9, 7, 5, 5]
`- [9, 9, 9, 7, 5, 5]
[9, 9, 9, 7, 5, 5]

最后,我会稍微整理一下装饰器,让它看起来更简洁,也更清楚你在做什么:

class traced(object):
    indent = 0

    def __init__(self, f):
        self.__name__ = f.__name__
        self.f = f

    def __call__(self, *args, **kwargs):
        if kwargs:
            l = [str(key) + '=' + str(value) for key, value in kwargs.items()]
        else:
            l = list(map(str, args))
        print('| ' * traced.indent + ',- {0} ({1})'.format(self.__name__, ', '.join(l)))
        try:
            traced.indent += 1
            value = self.f(*args,**kwargs)                
        finally:
            traced.indent -= 1

        print('| ' * traced.indent + '`- ' + repr(value))
        return value

在这里,我把所有的参数聚合简化成了列表推导式。同时,我使用了字符串格式化,让格式看起来更清晰。这样你也可以把原本需要加在列表内容周围的括号合并起来(顺便说一下,你在两个例子中都这样做了)。而且因为你重新抛出了异常,并没有真正去查看它,所以我们一开始并不需要捕捉这个异常,只需要确保在最终块中调整好缩进就可以了。

其实,为什么要检查是可变参数还是关键字参数呢?直接接受两者就行了:

l = list(map(str, args))
l.extend([str(key) + '=' + str(value) for key, value in kwargs.items()])

撰写回答