如何在Python中防止尝试捕获每一行代码?

22

我有很多连续的代码行可能会抛出异常,但无论如何都应该继续执行下一行。如何在不逐个捕获每个可能抛出异常的语句的情况下实现这一点?

try:
    this_may_cause_an_exception()
    but_I_still_wanna_run_this()
    and_this()
    and_also_this()
except Exception, e:
    logging.exception('An error maybe occured in one of first occuring functions causing the others not to be executed. Locals: {locals}'.format(locals=locals()))

看一下上面的代码,所有函数都可能抛出异常,但无论是否抛出异常,它仍应执行下一个函数。有没有一种好的方法可以做到这一点?

我不想这样做:

try:
    this_may_cause_an_exception()
except:
    pass
try:
    but_I_still_wanna_run_this()
except:
    pass
try:
    and_this()
except:
    pass
try:
    and_also_this()
except:
    pass
我认为只有当异常是关键性的(比如电脑会烧掉或整个系统会被搞砸),程序才应该停止运行,但对于许多小事情,也会抛出异常,例如连接失败等。我通常在异常处理上没有任何问题,但在这种情况下,我正在使用一个第三方库,它很容易因为小事情而引发异常。
在查看了m4spy的回答后,我想到是否有可能编写一个装饰器,即使其中一个函数行引发了异常,每一行代码都能继续执行。
像这样的东西可能不错:
def silent_log_exceptions(func):
    @wraps(func)
    def _wrapper(*args, **kwargs):
        try:
            func(*args, **kwargs)
        except Exception:
            logging.exception('...')
            some_special_python_keyword # which causes it to continue executing the next line
    return _wrapper

或者像这样:

def silent_log_exceptions(func):
    @wraps(func)
    def _wrapper(*args, **kwargs):
        for line in func(*args, **kwargs):
            try:
                exec line
            except Exception:
                logging.exception('...')
    return _wrapper



@silent_log_exceptions
def save_tweets():
    a = requests.get('http://twitter.com)
    x = parse(a)
    bla = x * x

4
这两种编码风格都不太符合Python的习惯用法...我不确定该提出什么建议,但期待着答案。 - Chris Pfohl
6个回答

27
for func in [this_may_cause_an_exception,
             but_I_still_wanna_run_this,
             and_this,
             and_also_this]:
    try:
        func()
    except:
        pass

这里有两件事情需要注意:

  • 您想执行的所有操作都必须由具有相同签名的可调用函数表示(在示例中,是不带参数的可调用函数)。如果它们还没有这样,可以使用小型函数、lambda表达式、可调用类等进行封装。
  • 裸的except子句是一个坏主意,但您可能已经知道了。

另一种更灵活的替代方法是使用高阶函数,如:

def logging_exceptions(f, *args, **kwargs):
    try:
        f(*args, **kwargs)
    except Exception as e:
        print("Houston, we have a problem: {0}".format(e))

是的,我知道,这就是为什么我在问题中加了第一个本地日志记录的原因。这是我最近习惯于记录我的本地内容以帮助在生产服务器上调试的新东西。我真的很喜欢你列出函数列表的方式,尽管在我的问题中所有的函数都是实际生活示例中大部分都是语句和保存变量。但我可以想办法解决这个问题。 - Sam Stoelinga

3

我遇到了类似的问题,在stackoverflow上提问。接受的答案处理了记录日志和只监视特定异常的情况。最终,我得到了一个修改过的版本:

class Suppressor:
    def __init__(self, exception_type, l=None):
        self._exception_type = exception_type
        self.logger = logging.getLogger('Suppressor')
        if l:
            self.l = l
        else:
            self.l = {}
    def __call__(self, expression):
        try:
            exec expression in self.l
        except self._exception_type as e:
            self.logger.debug('Suppressor: suppressed exception %s with content \'%s\'' % (type(self._exception_type), e))

可这样使用:

s = Suppressor(yourError, locals()) 
s(cmdString)

所以你可以设置一个命令列表,并使用抑制器 map 来运行所有命令。


3
永远不要将可变对象(例如List、Dictionary或某些情况下的object)设置为默认参数值。因为它们只会被评估一次,这将导致严重的内存泄漏和逻辑错误。http://docs.python.org/tutorial/controlflow.html#default-argument-values - FallenAngel

0
除了提供的答案之外,我认为值得注意的是,已经提出了单行try-except语句 - 请参见相关PEP 463,不幸的是被拒绝了。
""" I want to reject this PEP. I think the proposed syntax is acceptable given the
desired semantics, although it's still a bit jarring. It's probably no worse than the
colon used with lambda (which echoes the colon used in a def just like the colon here
echoes the one in a try/except) and definitely better than the alternatives listed.

But the thing I can't get behind are the motivation and rationale. I don't think that
e.g. dict.get() would be unnecessary once we have except expressions, and I disagree 
with the position that EAFP is better than LBYL, or "generally recommended" by Python. 
(Where do you get that? From the same sources that are so obsessed with DRY they'd rather
introduce a higher-order-function than repeat one line of code? :-)

This is probably the most you can get out of me as far as a pronouncement. Given that
the language summit is coming up I'd be happy to dive deeper in my reasons for rejecting
it there (if there's demand).

I do think that (apart from never explaining those dreadful acronyms :-) this was a 
well-written and well-researched PEP, and I think you've done a great job moderating the 
discussion, collecting objections, reviewing alternatives, and everything else that is 
required to turn a heated debate into a PEP. Well done Chris (and everyone who 
helped), and good luck with your next PEP! """

0
您可以使用装饰器来处理这样的任务:
import logging
from functools import wraps

def log_ex(func):
    @wraps(func)
    def _wrapper(*args, **kwargs):
        try:
            func(*args, **kwargs)
        except Exception:
            logging.exception('...')
    return _wrapper

@log_ex
def this_may_cause_an_exception():
    print 'this_may_cause_an_exception'
    raise RuntimeError()

@log_ex
def but_i_wanna_run_this():
    print 'but_i_wanna_run_this'

def test():
    this_may_cause_an_exception()
    but_i_wanna_run_this()

调用测试函数将会像这样(它将显示两个函数都已执行):

>>> test()
this_may_cause_an_exception
ERROR:root:...
Traceback (most recent call last):
  File "<stdin>", line 5, in _wrapper
  File "<stdin>", line 4, in my_func
RuntimeError
but_i_wanna_run_this

如果函数中的某一行引发了异常,那么它不会执行下一行,而是退出函数并记录异常日志,对吗? - Sam Stoelinga
@SamStoelinga 不,但下一个函数将被调用。我更新了上面的答案,所以应该更清晰了。这是与被接受的答案类似的方法,但您不必创建列表,您的函数也不需要具有固定的签名。 - schlamar
啊,是的,那真是我太蠢了;)这也是一个不错的解决方法,但仍然会让我写很多代码。我认为最好的解决方案是使用装饰器,它将允许整个函数执行,无论函数中的任何一行是否引发错误。不确定在Python中是否可能实现这一点。 - Sam Stoelinga

0
有时候,当语言无法支持你优雅地表达一个想法的方式,因为语言发展在过去几十年里确实失败了,你只能依靠Python仍然是一种动态语言这个事实,它支持exec语句,从而使下面的操作成为可能。
code="""
for i in range(Square_Size):
    Square[i,i] @= 1
    Square[i+1,i] @= 2
    @dowhatever()
"""

这个新的操作符使代码更加Pythonic和优雅,因为您不需要指定额外的if语句来保证索引保持在范围内或函数成功执行,这与我们要表达的内容完全无关(它只是不应该停止)。在Lisp中,可以轻松地以Lispy方式定义它,但似乎无法以优雅的方式在Python中定义它,但仍然有一个小的预解析器可以实现它: exec "\n".join([o+"try: "+z.replace("@","")+"\n"+o+"except: pass" if "@" in z else z for z in code.split("\n") for o in ["".join([h for h in z if h==" "])]]) #new <- hackish operator which wraps try catch into line 假设Square是4x4且仅包含零,则结果如下:
[1 0 0 0]
[2 1 0 0]
[0 2 1 0]
[0 0 2 1]

相关:Sage / Sagemath CAS使用预处理函数,在代码到达Python解释器之前进行转换。对于该函数的猴子补丁如下:
def new_preparse(code,*args, **kwargs):
    code="\n".join([o+"try: "+z.replace("@","")+"\n"+o+"except: pass" if "@" in z else z for z in code.split("\n") for o in ["".join([h for h in z if h==" "])]])
    return preparse(code)
sage.misc.preparser.preparse=new_preparse

-3
try:
    this_may_cause_an_exception()
except:
    logging.exception('An error occured')
finally:
    but_I_still_wanna_run_this()
    and_this()
    and_also_this()

您可以使用异常处理的finally块。实际上,它是用于清理代码的。

编辑: 我看到您说所有函数都可能抛出异常,在这种情况下,larsmans的答案是我能想到的最干净的方法,用于捕获每个函数调用的异常。


3
如果在but_i_still_wanna_run_this()函数中出现了异常,该怎么办? - shiva
这就是为什么我编辑了帖子,包括我的认识到Sam想要在每个函数中捕获异常。 - Christian Witts

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