Как поймать исключение в итераторе цикла for

Это цикл for в Python:

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

Это прерывается во второй строке. Я бы хотел, чтобы пропустить или зарегистрировать неудачные строки и продолжить.

Ответы

Ответ 1

Если ваш внутренний итерабель может быть продолжен после исключения, все, что вам нужно обернуть, это тривиальный генератор:

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

Например:

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 это означало бы чтение строк n из файла перед его переносом в csv.reader. В других случаях это может означать передачу n конструктору. В других случаях, как и в raisinggenfunc выше, это просто невозможно.

Ответ 2

Оказывается, если вы используете csv.reader в цикле for, вы можете покрыть это с помощью исключения try и цикл for будет продолжен. Здесь образец:

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

Если этот код сталкивается с внутренней ошибкой, он переходит к исключающему блоку и продолжает итерацию для следующей строки в файле CSV.

Ответ 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

Ответ 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() продолжает ошибку. Мне не нравится делать тестовый пример, но если это так, я подозреваю, потому что он реализован как модуль C, найденный здесь. Мой C не слишком резкий, поэтому я не слишком разбираюсь в этом, но достаточно сказать, что я не думаю, что это возможно.

EDIT: я не ответил на ваш вопрос напрямую. Сделайте то, что 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 элементов. Дайте мне знать, если вам нужен более полный пример.