Я настроил рабочее приложение на Python. В рамках этого работника я всегда требую, чтобы в возврате был определен new_process_time
, чтобы внешняя очередь могла правильно его обработать.
Иногда может возникнуть серьезная ошибка, которая должна полностью прервать обработку этой итерации. Это нежелательно, но ожидаемо. В этом случае в качестве возврата выбирается время по умолчанию.
Моя проблема возникает, когда возникает ошибка, достойная прерывания, после того, как следующий new_process_time
уже найден. В идеале я бы хотел и вызвать, и зарегистрировать эту ошибку, одновременно сохраняя new_process_time
для передачи в очередь вместо значения по умолчанию.
Я могу придумать 2 реализации:
Перехватите ошибку, верните ее вместе с переменной и повторно вызовите ее, прерывая более широкий рабочий контекст:
def foo(new_process_time):
condition = True
e = None
try:
new_process_time = 10 # Calculated within foo() through context only available in foo()
if condition:
raise ValueError("Stuff broke.")
... # logic I wish to abort if an exception is raised
except ValueError as e:
print('Error triggered')
return new_process_time, e
return new_process_time, e
def worker():
new_process_time = None
try:
new_process_time, e = foo(new_process_time)
if e is not None:
raise e
... # logic I wish to abort if an exception is raised
except Exception as e:
print(f"Logging the exception: {e}")
finally:
if new_process_time is not None:
print(f"Next schedule: {new_process_time}")
else:
print(f"Next schedule: DEFAULT")
worker()
Используйте генератор:
def foo_2(new_process_time):
condition = True
try:
new_process_time = 10 # Calculated within foo() through context only available in foo()
yield new_process_time
if condition:
raise ValueError("Stuff broke.")
... # logic I wish to abort if an exception is raised
except ValueError as e:
print('Error triggered')
raise e
def worker_2():
new_process_time = None
try:
try:
foo_gen = foo_2(new_process_time)
new_process_time = next(foo_gen) # handle the logic up to new_process_time
next(foo_gen) # handle the rest of the logic
except StopIteration:
print('this is fine, continue as normal')
... # logic I wish to abort if an exception is raised
except Exception as e:
print(f"Logging the exception: {e}")
finally:
if new_process_time is not None:
print(f"Next schedule: {new_process_time}")
else:
print(f"Next schedule: DEFAULT")
worker_2()
Я не фанат ни одной из этих реализаций. Хотя они оба работают, я чувствую, что они (опция генератора больше, чем возврат) не являются питоническими и содержат антишаблоны. Проще говоря, напрашивается на неприятности.
В качестве альтернативы, я считаю, что global
здесь может быть вариантом, но учитывая множество причин избегать его. Я полагаю, что если рабочая функция выполняется параллельно, это может вызвать проблемы.
Как гарантировать, что переменная, требуемая в более высокой области видимости, но не имеющая возможности нормально вернуться из-за ошибки, которая должна быть вызвана, все еще доступна?
Поместите неизменяемую переменную в изменяемый блок:
from dataclasses import dataclass
@dataclass
class TimeContext:
new_process_time: int | None = None
Тогда foo
не нужно беспокоиться об ошибках.
def foo(time_context: TimeContext):
condition = True
time_context.new_process_time = 10 # this is reflected in the outer function
if condition:
raise ValueError("Stuff broke.")
И worker
не нужно разворачивать ошибки:
def worker():
time_context = TimeContext()
try:
foo(time_context)
except Exception as e:
print(f"Logging the exception: {e}")
finally:
if time_context.new_process_time is not None:
print(f"Next schedule: {time_context.new_process_time}")
else:
print(f"Next schedule: DEFAULT")
Думаю, мой вопрос на самом деле сводится к тому, что «тип данных неизменяем, что теперь?» такой простой ответ, как «сделать его изменчивым», просто потрясающий. Позвольте мне протестировать это в моей более широкой реализации, чтобы убедиться, что это работает.