【问题标题】:How to catch an exception in the for loop iterator如何在 for 循环迭代器中捕获异常
【发布时间】:2012-11-30 21:57:35
【问题描述】:

这是 Python 中的 for 循环:

for_stmt ::=  "for" target_list "in" expression_list ":" suite

通常,当从expression_list 产生一个值引发异常时,循环中止。有没有一种优雅的方法(不使用while True 或类似的东西重写循环)来捕获这个异常并继续循环?

这是一个例子:

import csv

csv.field_size_limit(10)

reader = csv.reader(open('test.csv', 'r'))
for line in reader:
    print(line)

使用此文件:

foo,bar,baz
xxx,veryverylong,yyy
abc,def,ghi

这将在第二行中止。我想要一种方法来跳过或记录失败的行并继续。

【问题讨论】:

  • 类似问题:stackoverflow.com/questions/7472786/…,但在这种情况下我无法更改生成器。
  • 为什么不只创建一个调用另一个生成器的生成器,而是使用try/except
  • @StevenRumbalski 这就是我的想法,但是对于csv.reader,这不是真的(我自己试过了)。它引发了一个_csv.Error,但是在reader 上对next() 的另一个调用将在引发错误的行之后返回下一个,并且它会像往常一样继续到StopIteration
  • 你能解释一下为什么 try: print(line) except: continue 不起作用吗?
  • @mehtunguh raise 本质上发生在 reader.next() 中,它在 for 循环本身中被调用。 print 是事后的事,所以 try/catch 在它附近是行不通的。

标签: python exception


【解决方案1】:

如果你的内部迭代可以在异常之后继续,你只需要包装一个简单的生成器:

def wrapper(gen):
  while True:
    try:
      yield next(gen)
    except StopIteration:
      break
    except Exception as e:
      print(e) # or whatever kind of logging you want

例如:

In [9]: list(wrapper(csv.reader(open('test.csv', 'r'))))
field larger than field limit (10)
Out[9]: [['foo', 'bar', 'baz'], ['abc', 'def', 'ghi']]

另一方面,如果内部迭代器在异常后无法继续,则无法包装它:

def raisinggenfunc():
    yield 1
    raise ValueError("spurious error")
    yield 3

In [11]: list(wrapper(raisinggenfunc()))
spurious error
Out[11]: [1]

通过调用 Python 生成器函数或评估生成器表达式创建的任何生成器都将不可恢复。

在这种情况下,您需要找到某种方法来创建一个新的迭代器来恢复迭代。对于csv.reader 之类的内容,这意味着在将文件包装到csv.reader 之前从文件中读取n 行。在其他情况下,这可能意味着将n 传递给构造函数。在其他情况下——如上面的raisinggenfunc,这是不可能的。

【讨论】:

  • 您的假设没有实际意义,因为我们已经确定 csv.reader 是可继续的。
  • 泛化很少有害。
  • 不应该是except StopIteration: break而不是raise吗?
  • 漂亮!这帮助我跳过了编码错误的行
  • 谢谢您-聪明且解释清楚!但遗憾的是没有更优雅的方式来做到这一点。
【解决方案2】:

事实证明,如果您在 for 循环中使用 csv.reader,那么您可以使用 try 异常覆盖它,并且 for 循环将继续。这是一个示例:

reader=csv.reader
try:
   for row in reader:
      if row[0]=='type':
         datarows.append(row)
except: continue

如果此代码遇到内部错误,它会跳转到 except 块并继续迭代 CSV 文件中的下一行。

更新:这现在给出了一个错误,正如 cmets 中指出的那样,尽管我已经在旧版本的 2.7 中成功使用它

【讨论】:

  • SyntaxError: 'continue' not properly in loop 在 python 2.7 和 3.x 中
  • 这似乎是一个新错误。我过去曾成功地使用它来跳过错误的行。
  • 在 Python 3.6.9 中为我工作
【解决方案3】:

您可以将阅读器包装在另一个迭代器中,然后根据需要处理异常。

class ExceptionHandlingIterator(object):
    def __init__(self, iterable):
        self._iter = iter(iterable)
        self.handlers = []
    def __iter__(self):
        return self
    def next(self):
        try:
            return self._iter.next()
        except StopIteration as e:
            raise e
        except Exception as e:
            for handler in self.handlers:
                handler(e)
            return self.next()

csv_reader = ExceptionHandlingIterator(csv.reader(open('test.csv', 'r'))
# attach handlers to the reader here
for line in csv_reader:
    print line

【讨论】:

  • 如果内部可迭代在异常后无法继续,这将不起作用。 (尝试在pastebin.com/nZsyVfpe 中用ExceptionHandlingIterator 替换wrapper,你会发现你仍然得到[1] 而不是[1, 3]。)如果内部可迭代可以继续,我不要认为它会为更简单的迭代器添加任何内容。
  • 但是在OP的问题中,迭代器可以在异常之后继续,并且他希望能够自定义如何处理异常,使用一个或多个异常处理规则。这正是增加了这种能力。您可以为每个处理程序情况一次性使用迭代器/生成器,但这只是重复代码。
  • 但是您仍然不需要构建与生成器函数完全相同的自定义迭代器。它需要更多的知识来编写,更容易出错,而且它有更多的样板文件妨碍阅读。
  • 如果您有超过 1 组想要以相同模式实现的日志记录/处理,您最终会得到更多样板,因为您必须在每种情况下重复此逻辑。这将定义如何处理异常的工作与迭代机制分开。用户只需要知道异常的接口即可;所有关于迭代的细节都被隐藏了。
  • 您可以将相同的handler 回调添加到生成器;您仍然不需要显式构建一个与迭代器协议匹配的类,这样它就可以做与生成器完全相同的事情。
【解决方案4】:

很遗憾,这在纯 Python 中是不可能的。

观察以下代码:

def testIter(n):
    count = 0
    while count<n:
        try:
            for i in xrange(count,n):
                if i == 3:
                    raise Exception("Asdfas")
                count = count + 1
                yield i
        except:
            continue

这会输出以下内容:

x = testIter(10)
x.next()  # 0
x.next()  # 1
x.next()  # 2
x.next()  # Exception: Asdfas
x.next()  # Exception: StopIteration

人们会期望它在 while 循环的新迭代中继续,但事实并非如此。

有些人表示 csv.reader() 继续出错。我不想为它做一个测试用例,但如果是这样,我怀疑这是因为它是作为找到here 的 C 模块实现的。我的 C 不是太尖锐,所以我没有深入研究它,但足以说我认为不可能。

编辑:我没有直接回答你的问题。在可以恢复的迭代器的情况下执行 abarnet 所说的操作(这意味着它是 C 迭代器)。

编辑 2:实际上并非完全正确。

class myInformativeException(Exception):
    def __init__(self, count):
        self.count = count

def testIter(n):
    for i in xrange(n):
        if i==4:
            raise myInformativeException(i)
        yield i

def iterwrap(n):
    x = testIter(n)
    try:
        for i in x:
            yield i
    except myInformativeException as e:
        print "Error on ", e.count

打印出来:

0
1
2
3
Error on 4

所以,如果您能够在 X 元素之后创建一个迭代器,那么显然是有可能的。如果您需要更完整的示例,请告诉我。

【讨论】:

  • 您的 EDIT 2 版本在出现错误后无法继续,那么这表明了什么?
  • 在出现错误之前您进入迭代器的距离。由此,另一个包装函数理论上可以使新的迭代器在此之后继续。在纯 Python 中。
  • 所以想法是使用异常将“光标”传递回调用代码。好主意^^
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-06
  • 1970-01-01
  • 1970-01-01
  • 2017-02-17
  • 2022-12-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多