Ошибки в цикле Python: try внутри for и продолжение после сбоя
Сто строк данных, одна битая — и без защиты падает весь проход. Ставим try вокруг одного элемента: сбой обрабатывается, цикл идёт дальше, сбои считаются и попадают в журнал.
Редакция Питоники
Настоящие данные почти всегда приходят с мусором: в файле цен затесалось слово, в таблице пустая ячейка, в выгрузке число с пробелом посреди. Урок 2 показал try и except на одной операции — здесь та же пара встаёт на самое рабочее место программы: внутрь цикла. Задача урока — превратить цикл, который падает на первом же мусоре, в цикл, который переживает сбой, считает его и идёт дальше.
Сбой посреди цикла
Сначала посмотрим на проблему в чистом виде. Цикл разбирает список цен, и одна из четырёх строк — не число:
# Так не запускается: на третьем элементе int упадёт
prices = ['100', '200', 'ноль', '400']
for price in prices:
value = int(price)
print('цена:', value)
Упало на третьем элементе — четвёртый даже не взглянули. Для отчёта это худший исход: вместо частичного результата ни одного. Именно поэтому сбой в цикле не глотают и не игнорируют, а обрабатывают по одному элементу.
try внутри for: сбой одного элемента
Решение — перенести try внутрь тела цикла и сузить до одной операции. Каждый шаг цикла получает собственный запасной план:
prices = ['100', '200', 'ноль', '400']
for price in prices:
try:
value = int(price)
print('цена:', value)
except ValueError:
print('пропущено:', price)
print('цикл закончился')
цена: 100 цена: 200 пропущено: ноль цена: 400 цикл закончился
Это главный шаблон урока: одна итерация — одна попытка. try стоит внутри тела, поэтому падает не цикл, а только разбор текущего элемента; except его обрабатывает, и цикл автоматически продолжается со следующего. Отдельный continue не нужен: тело и так подошло к концу. А break здесь не появился бы и с ним — обрывать проход из-за одной строки ни к чему.
Ловим не всё, а конкретное
Внутри цикла соблазн widest-перехвата выше, чем где-либо: тысяча итераций, тысяча шансов на неожиданное. Но именно в цикле широкая сетка из прошлого урока опаснее всего — она спрячет и опечатку в теле, промахнувшуюся мимо данных. Правило то же: ловить конкретный тип, а разные риски разбирать разными except-блоками из урока 4:
rows = [
{'name': 'Анна', 'age': '30'},
{'age': '41'},
{'name': 'Вера', 'age': 'abc'},
]
for row in rows:
try:
print(row['name'], '-', int(row['age']))
except KeyError as e:
print('нет ключа:', e)
except ValueError:
print('возраст не число')
Анна - 30 нет ключа: 'name' возраст не число
У каждой строки — свой исход: у второй нет ключа, у третьей мусор вместо возраста. Цепочка except разбирает оба случая по именам, и цикл ни разу не споткнулся. Заметьте: сообщение KeyError печатается через переменную e из урока 5 — оно само и есть имя потерянного ключа, взятое в кавычки.
Счётчики: успешные и сбои
Печать по ходу удобна в моменте, но отчёту нужны числа: сколько строк разобралось, сколько поломалось. Это работа для счётчиков-аккумуляторов из урока 11 циклов — по одному на исход:
values = ['10', 'x', '20', '', '30']
ok = 0
bad = 0
for text in values:
try:
int(text)
ok += 1
except ValueError:
bad += 1
print('разобрано:', ok)
print('сбоев:', bad)
разобрано: 3 сбоев: 2
Пустая строка тоже не разобралась — int('') падает с тем же ValueError, отдельной проверки она не потребовала. Доля сбоев — это диагноз данных: две из пяти — терпимо и обрабатываем, пятьдесят из ста — источник сломан, о чём чуть ниже. Важно, где стоят счётчики: ok увеличивается внутри try только после успешно выполненной операции — порядок строк решает, честен ли счёт.
else: успех как отдельная ветка
else из урока 6 внутри цикла разделяет попытку и результат: try — только рискованная строка, else — то, что делается с добытым значением:
values = ['10', 'x', '30']
for text in values:
try:
number = int(text)
except ValueError:
print('сбой:', text)
else:
print('квадрат:', number * number)
квадрат: 100 сбой: x квадрат: 900
Журнал сбоев с номерами
Пропущено в моменте — хорошо, а какой именно элемент пропущен — знает только тот, кто стоял у экрана. Отчёт собирает это сам: номер, значение и сообщение ошибки. Нумерацию даёт enumerate со стартом с единицы, сообщение — переменная из except ... as e:
values = ['10', 'x', '20', '3.5']
errors = []
for n, text in enumerate(values, 1):
try:
int(text)
except ValueError as e:
errors.append(f'строка {n}: {text!r} - {e}')
for line in errors:
print(line)
строка 2: 'x' - invalid literal for int() with base 10: 'x' строка 4: '3.5' - invalid literal for int() with base 10: '3.5'
Спецификация {text!r} печатает значение в кавычках — так видно и пустую строку, и строку с пробелами, которые в обычной печати растворились бы. Журнал здесь собран списком строк, и в финальном проекте раздела этот приём дорастёт до настоящего отчёта с первыми тремя ошибками и сверкой.
Когда переставать
Продолжать после сбоя — правило по умолчанию, но не догма. Если сбоев половина, источник данных сломан целиком, и молотить его дальше — тратить время и плодить журнал. Для этого случая у цикла есть порог: счётчик сбоев дотянул до лимита — break из урока про while останавливает проход:
values = ['10', '20', 'ошибка', 'ошибка', '30']
ok = 0
bad = 0
for text in values:
try:
int(text)
ok += 1
except ValueError:
bad += 1
if bad >= 2:
print('слишком много сбоев, останавливаюсь')
break
print(f'разобрано: {ok}, сбоев: {bad}')
слишком много сбоев, останавливаюсь разобрано: 2, сбоев: 2
Второй сбой подряд закрыл проход: порог два сработал, и строка 30 осталась неразобранной — сознательная плата за быстрый отказ. Порог выбирают по данным: для аккуратной выгрузки хватит и трёх сбоев на тысячу строк, для уличного шума — и двадцати на сотню.
| Что случилось с элементом | Что делает цикл |
|---|---|
| Разобрался штатно | Обрабатывает и идёт дальше |
| Не разобрался — редкий мусор | except: логирует и продолжает |
| Сбоев стало слишком много | Порог + break: быстрый отказ |
| Ошибка вне данных — опечатка в теле | Не ловить: пусть падает и показывает строку |
# try обнимает всё тело: какая строка упала - неизвестно
for row in rows:
try:
name = row['name']
age = int(row['age'])
print(name, age)
except Exception:
pass
import logging
logging.warning('строка %s: не разобралось - %s', n, e)
Комплект собран: try вокруг одного элемента, конкретный тип, счётчики двух исходов, журнал с номерами, порог для остановки. Следующий урок отвечает на вопрос, который встаёт, когда сбои нужно не гасить, а перевыпускать: raise from — цепочка причин, где новая ошибка несёт с собой старую.
Сначала предскажи ответ в голове — это главный навык программиста.
values = ['4', '', '6']
ok = 0
for text in values:
try:
int(text)
ok += 1
except ValueError:
pass
print(ok)
for text in ['a', '3']:
try:
n = int(text)
except ValueError:
print('сбой:', text)
else:
print('число:', n)
print('конец')
ok = 0
bad = 0
for text in ['1', 'x']:
ok += 1
try:
int(text)
except ValueError:
bad += 1
print(ok, bad)
1. Куда поставить try, чтобы сбой одного элемента не ронял цикл?
2. Какие счётчики заводят для цикла с разбором данных?
3. Что даёт enumerate(values, 1) в цикле с журналом сбоев?
4. Когда в цикле с обработкой ошибок уместен break?
5. Почему в цикле не советуют except Exception вместо конкретного типа?
6. Нужен ли continue после except внутри for?
7. Что напечатает {text!r} для строки с пробелами по краям?
Дан список readings с показаниями датчиков: среди чисел затесались пустая строка и слово сбой. Пройдите циклом с try внутри: каждое значение разберите int-ом, успешные складывайте в numbers, сбои считайте счётчиком bad. В конце напечатайте список чисел и строку сбоев: N.
Как обработать ошибку в цикле Python и не остановить цикл?
Поставить try внутрь тела цикла вокруг операции с одним элементом: for text in values: try: int(text) except ValueError: ... Сбой обрабатывается в except, и цикл сам продолжается со следующего элемента — continue не нужен.
Урок 2: try и exceptКак пропустить битые строки и посчитать их?
Завести счётчик bad с нуля до цикла и увеличивать его в except, а успешные значения складывать в отдельный список. После цикла счётчики покажут состав данных: сколько разобралось, сколько поломалось.
Урок 11 циклов: счётчики и аккумуляторыКак узнать, на какой строке списка произошёл сбой?
Идти циклом с enumerate(values, 1) — он отдаёт пары номер-значение с нумерацией от единицы. В except номер и значение дописываются в журнал: errors.append(f'строка {n}: {text}') — и отчёт показывает все места сбоев сразу.
Когда цикл с обработкой ошибок стоит остановить принудительно?
Когда сбоев слишком много: если половина элементов не разбирается, источник сломан целиком и продолжение только тратит время. Заводят порог — if bad >= LIMIT: break — и проход останавливается на первом превышении.
Урок 5: цикл while и breakЧто лучше в цикле: except Exception или конкретный тип?
Конкретный тип: в теле цикла рядом с данными живёт и код, и широкая сетка спрячет опечатку наравне с ожидаемым мусором. Разные риски одной строки — разные except-блоки: KeyError отдельно, ValueError отдельно.
Урок 15: except ExceptionПонравился урок? Сошлитесь на него
«Считать успехом можно только завершённую работу — инкремент ставится после рискованной операции или в else-ветке.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Исключения · Урок 15
except Exception в Python: когда ловить всё и чем это грозит
Одна строка вместо перечня типов: except Exception ловит почти всё. Разбираем слепую зону перехвата, спрятанные баги и место, где широкая сетка уместна по-настоящему.
Исключения · Урок 2
try и except: ловим ошибку и не падаем
Первый запасной план: try держит рискованный код, except — что делать, если он упал. Прыжки, пропуски и try внутри цикла — на живом коде.
Исключения · Урок 17
raise from в Python: цепочка причин, __cause__ и __context__
Обработчик превращает чужую ошибку в свою — а куда девается первоначальная? Python хранит её в цепочке: raise from e помечает причину явно, from None прячет её сознательно.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
aiogram · Урок 9
Обработка ошибок в телеграм-боте: try/except, лимиты и логи
Учим бот переживать ошибки: try/except в хендлерах, лимиты Telegram с RetryAfter, модуль logging вместо print и перезапуск polling после падения.
обработка ошибок pythonошибки телеграм бота
Циклы · Урок 4
enumerate в Python: индекс и значение вместе
Нужен номер элемента вместе с самим элементом? enumerate отдаёт циклу пары «индекс — значение»: без ручного счётчика и с нумерацией с единицы через start=1.
enumerate pythonpython enumerate
Исключения · Урок 3
except ValueError: ловим конкретный тип ошибки
Уточняем обработчик: except ValueError ловит только свой тип ошибок. Почему голый except прячет опечатки и как ValueError ведёт себя у int и list.remove.
except valueerror pythontry except python