Один Future упал: как собрать остальные результаты Python
Проверка нескольких независимых адресов выполняется через ThreadPoolExecutor. Один адрес возвращает ошибку, и весь отчёт обрывается на первом future.result(). При этом соседние проверки могли уже закончиться успешно. Пользователю нужен отчёт по каждому адресу, поэтому правило «одна ошибка отменяет всё» здесь выбрано случайно структурой цикла.
Сохранить связь работы с ID
Future хранит результат или исключение отдельного вызова. Метод result возвращает значение, а при неудаче снова возбуждает исключение этой работы в потоке сборщика. Это удобное место для перевода технического отказа в запись отчёта. Важно окружить try именно получение одного результата, а не весь обход: тогда следующая завершённая работа всё ещё будет обработана.
as_completed выдаёт futures по мере завершения. Он не обещает порядок исходных аргументов, поэтому идентификатор задания необходимо сохранить отдельно. Словарь future_to_id связывает объект выполнения с адресом или ключом входа. В финальном отчёте можно восстановить нужный порядок по ID, не заставляя быстрые ответы ждать предыдущую медленную работу при сборе.
Пример использует чистую функцию с одной предсказуемой ошибкой. Здесь не нужны настоящие сетевые адреса: проверяем контракт сборщика, а не доступность сайта. Один результат содержит вычисленный квадрат, второй — текст отказа. Проверки требуют две успешные записи и одну неуспешную, сохраняя связь с исходными заданиями.
Собрать частичный отчёт
from concurrent.futures import ThreadPoolExecutor, as_completed
def inspect_item(item_id):
if item_id == 2:
raise ValueError("invalid input")
return item_id * item_id
results = {}
errors = {}
with ThreadPoolExecutor(max_workers=2) as pool:
future_to_id = {pool.submit(inspect_item, item_id): item_id for item_id in [1, 2, 3]}
for future in as_completed(future_to_id):
item_id = future_to_id[future]
try:
results[item_id] = future.result()
except ValueError as exc:
errors[item_id] = str(exc)
assert results == {1: 1, 3: 9}
assert errors == {2: "invalid input"}
assert set(results) | set(errors) == {1, 2, 3}
assert not set(results) & set(errors)
print("results:", sorted(results.items()))
print("errors:", sorted(errors.items()))
Порядок печати специально нормализуется через sorted. Время планирования потоков не должно менять содержимое проверяемого отчёта. Если порядок завершения сам является частью диагностики, сохраняйте его отдельным полем, а успешность и принадлежность задания проверяйте независимо. Ожидать заранее известную последовательность futures на разных компьютерах нельзя.
Завершение и размер очереди
Перехватывайте ошибки, для которых определено поведение. Здесь ValueError означает неправильный вход отдельного задания. В реальном клиенте отдельно обработайте сетевые таймауты и нарушения контракта ответа. Не превращайте любой Exception в безымянное None: вызывающему коду важно различать успешное отсутствие данных и неудачную проверку.
Контекстный менеджер executor при выходе ожидает завершения работ. Поэтому исключение в сборщике не означает мгновенную остановку потоков. У уже выполняющейся функции нет универсальной безопасной принудительной отмены. Если операция обращается к сети, её собственные таймауты и поддержка остановки должны быть предусмотрены внутри. Бюджет ожидания futures и таймаут сетевого обмена решают разные задачи.
Для огромного входа словарь со всеми submit тоже становится источником расхода памяти. Ограничение max_workers задаёт число работающих потоков, но не число заранее отправленных заданий. При необходимости поддерживайте окно незавершённых futures и добавляйте новый вход после освобождения места. Сначала определите требуемую частичную отчётность, затем выбирайте размер окна.
Проверьте сценарии без ошибок, с несколькими ошибками и с пустым входом. Для каждого входного ID должен появиться ровно один исход. Если после отказа одной независимой проверки остальные обязаны завершиться, это свойство следует зафиксировать явно. Тогда будущая замена executor или способа обхода не поменяет пользовательский смысл отчёта.
Продолжить практику
Продолжите тему конкурентного исполнения и ошибок в «Python для профессионалов», где программа охватывает асинхронность и устройство выполнения Python. Откройте программу и выберите следующий разбор по своей нагрузке: ожидание ввода-вывода и вычислительная работа требуют разного решения, а результаты каждой задачи должны оставаться наблюдаемыми.
Продолжить на практике
- Python для профессионалов ↗
Python для профессионалов — курс для людей, стремящихся углубить свои знания языка. Вы изучите итераторы, генераторы, декораторы, современные возможности Python (3.8+), освоите метапрограммирование, асинхронность, внутреннее устройство интерпретатора, инструменты оптимизации и профилирования, а также получите уверенность в использовании продвинутых возможностей стандартной библиотеки.
