【问题标题】:How to trap an exception that occurs in code underlying Python for-loop?如何捕获 Python for 循环底层代码中发生的异常?
【发布时间】:2016-06-17 20:43:48
【问题描述】:

我仍然没有解决方案。

我正在编写一个邮件处理程序。我有一个很大的 INBOX 文件(由 Thunderbird 下载),里面有我的 gmail。

它在一些邮箱上运行,但不是来自 gmail 的收件箱。它持续了很长时间,但随后我收到了 UnicodeDecodeError 异常和一条消息 'ascii' codec can't decode byte 0xe2 in position 56: ordinal not in range(128).

修复解码逻辑是一种可能,但在尝试了各种解码字符串后,我要么仍然得到异常,要么错过处理大多数消息。

我接受某些消息可能无效或无法解码的可能性。跳过它们是可以的,但我不知道该怎么做,因为在运行 for 循环实现的底层代码时会发生异常,因此我无法使用 try/except 来捕获和跳过错误消息。

回溯只包含我程序中的一行,即这一行:

for message in mbox:

这似乎调用了itervalues,它调用了__getitem__,它在mailbox.py 中调用了get_message。我不知道 Python 中 for 循环的机制,但 itervalues 似乎是 for 循环遍历 mbox 中所有消息的方式,它通过调用通用 __getitem__ 来实现这一点,它调用 @987654328 @。

如果单个消息有问题,那很好,但我想跳过它并继续前进。问题是,由于我没有进行任何 API 调用,我不知道在哪里放置 try / except 处理程序。我想我可以用处理程序包装整个 for 循环,但这不允许我继续下一条记录。

我可以用几行代码重现问题:

import mailbox
mbox = mailbox.mbox('INBOX')
print(str(mbox.__len__()) + ' messages in mbox')
processed=0
for message in mbox:
    processed += 1
    if processed % 10000 == 0:
        print('processed ', processed, ' so far')

异常发生在文件中包含近 200k 的 30k 条消息之后的某处。

有人可以建议如何捕获异常,让我跳过损坏的异常并继续吗?

更新:这是异常产生的回溯:

Traceback(最近一次调用最后一次):

  File "C:\Users\Mark Colan\.p2\pool\plugins\org.python.pydev_4.5.5.201603221110\pysrc\pydevd.py", line 1529, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "C:\Users\Mark Colan\.p2\pool\plugins\org.python.pydev_4.5.5.201603221110\pysrc\pydevd.py", line 936, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "C:\Users\Mark Colan\.p2\pool\plugins\org.python.pydev_4.5.5.201603221110\pysrc\_pydev_imps\_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "D:\Dev\Sandbox2\Sandbox2.py", line 6, in <module>
    for message in mbox:
  File "C:\Program Files\Python35\lib\mailbox.py", line 108, in itervalues
    value = self[key]
  File "C:\Program Files\Python35\lib\mailbox.py", line 72, in __getitem__
    return self.get_message(key)
  File "C:\Program Files\Python35\lib\mailbox.py", line 779, in get_message
    msg.set_from(from_line[5:].decode('ascii'))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 56: ordinal not in range(128)

【问题讨论】:

  • 请提供完整的堆栈跟踪,用文字描述它几乎没有提供它那么有用。
  • 简单的建议,try: ... except UnicodeDecodeError as e: print e; pass;-) 围绕那个循环或更紧,直到......你找到了“地方”或处理了有趣的消息。
  • @Dilettant print e 不会像原始回溯消息那样为您提供几乎一样多的信息,这样做的目的是什么?
  • 我添加了回溯。我不认为在 for 循环周围放置 track/except 有助于隔离问题,因为它似乎不在迭代的代码中,而是在 for 迭代处理本身中。
  • 我想建议try: ... except UnicodeDecodeError: pass,但感觉更需要调查 - 这是欧洲的一个深夜。说真的:OP 似乎更多地是在跳过业务,而不是在考古学 ;-) ...印刷品似乎是一个平衡的妥协。

标签: python exception for-loop


【解决方案1】:

UnicodeDecodeError 并不意味着您的邮箱已损坏,但它包含超出 Ascii 范围的字符。听起来您可以跳过损坏的消息(如果您对前三万条消息没有问题,整个文件中可能不会超过少数),但实际上不是更好解决问题?

根据mailbox 文档,消息以二进制格式从文件中读取;当邮箱尝试将它们转换为 Unicode 并假定为 ASCII 编码时,您会收到错误消息。因此,请尝试提供一个“工厂方法”来进行自己的转换,然后委托给默认类:

def mbox_reader(stream):
    """Read a non-ascii message from mailbox"""
    data = stream.read()
    text = data.decode(encoding="utf-8")
    return mailbox.mboxMessage(text)

mbox = mailbox.mbox('INBOX', factory=mbox_reader)
for message in mbox:
    ...

试试这个,如果仍然出现错误,请将编码从 "utf-8" 更改为 "latin-1",或者任何可能是 Thunderbird 的默认值。如果还是不行,你仍然可以通过告诉python用特殊符号替换不可读的字符来阅读问题信息:

text = data.decode(encoding="utf-8", errors="replace")

使用此设置,您只会在消息中看到那个有趣的问号字形,而不是 UnicodeDecodeError

【讨论】:

  • 我会试试的。是的,总是更好地解决问题而不是掩盖它,这是我的方法。但我是 Python 新手,电子邮件编码的复杂性远远超出了我的经验。
  • 我会假设该消息会有一个标头说明使用了哪种编码。您的代码将其切换为始终使用 utf-8 而不是 ascii。如果不是 unicode,文件中的所有消息是否可能都是其中之一?
  • 好问题,你可能是对的。我真的不知道。从我的工厂函数开始,您可以捕获一个 UnicodeDecodeError (只需坚持“ascii”编码)并打印出当前未解析的消息,看看它真正提供了什么。我猜你现在可以看到如何做到这一点了。
  • 文件 "D:\Dev\Sandbox2\Sandbox2.py",第 7 行,在 check_each 中产生 next(it) 文件 "C:\Program Files\Python35\lib\mailbox.py",行108、在itervalues value = self[key]文件“C:\Program Files\Python35\lib\mailbox.py”,第75行,在getitem中返回self._factory(file)文件“D: \Dev\Sandbox2\Sandbox2.py",第 17 行,在 mbox_reader 中 text = data.decode(encoding="utf-8") UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 in position 8000: invalid start byte
  • 好的,所以它不是 utf-8。尝试"latin-1" 或错误处理程序。 (使用 latin 1 应该不会出现任何错误,只是偶尔会出现垃圾。)
【解决方案2】:

for 循环的内部机制是这样的:

#for message in mbox:
#    do_stuff()

it = iter(mbox)
try:
    while True:
        message = next(it)
        do_stuff()
except StopIteration:
    pass

因此您可以手动处理迭代器或使用生成器在引发错误时捕获其他异常:

import traceback
def check_each(iterable):
    it = iter(iterable)
    while True:
        try:
            yield next(it)
        except StopIteration:
            return
        except Exception as e:
            print("Exception was caught!")
            traceback.print_exc()
            continue #keep going

然后您可以执行for message in check_each(mbox):,它将显示每次发生错误时的完整回溯,而不会停止您的程序。

【讨论】:

  • 我在文件顶部添加了回溯代码,然后将 for 循环更改为“for message in check_each(mbox):” 现在我得到 TypeError: 'mbox' object is not an iterator on yield() 行。
  • 我的错,应该是yield next(it),对不起。
  • 它跑到了和以前差不多的地方。我知道我找到了您的处理程序,因为出现“异常被捕获”消息,但它没有继续,而是刚刚终止。
  • 好吧,显然我没有使用很好的测试用例,不,这似乎无法解决您的问题.. :(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多