Reputation: 3770
Can a context manager cause the function it is in to return
when handling an exception?
I have try-except pattern that is common to several methods that I'm writing, and I'm hoping to DRY it up with a context manager. The function needs to stop processing if there is an Exception
.
Here is an example of my current implementation:
>>> class SomeError(Exception):
... pass
...
>>> def process(*args, **kwargs):
... raise SomeError
...
>>> def report_failure(error):
... print('Failed!')
...
>>> def report_success(result):
... print('Success!')
...
>>> def task_handler_with_try_except():
... try:
... result = process()
... except SomeError as error:
... report_failure(error)
... return
... # Continue processing.
... report_success(result)
...
>>> task_handler_with_try_except()
Failed!
Is there a way to DRY the try-except so that the task-handler function returns if SomeError
was raised?
NOTE: The task-handler is being called by code in a library that does not handle exceptions generated from the task-handler function.
Here is one attempt, but it causes an UnboundLocalError
:
>>> import contextlib
>>> @contextlib.contextmanager
... def handle_error(ExceptionClass):
... try:
... yield
... except ExceptionClass as error:
... report_failure(error)
... return
...
>>> def task_handler_with_context_manager():
... with handle_error(SomeError):
... result = process()
... # Continue processing.
... report_success(result)
...
>>> task_handler_with_context_manager()
Failed!
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 6, in task_handler_with_context_manager
UnboundLocalError: local variable 'result' referenced before assignment
Is it possible to use a context manager to DRY this pattern up, or is there an alternative?
Upvotes: 2
Views: 816
Reputation: 55207
No, context managers can't do that, since you can only return
from a function within its body.
However, what you're looking for does exist! It's called a decorator.
def handle_errors(func):
def inner(*args, **kwargs):
try:
return func(*args, **kwargs)
except SomeError as error:
report_failure(error)
return
return inner
@handle_errors
def task_handler():
result = process()
report_success(result)
Note that if you always want to report_success
too, you can DRY this up even more!
def report_all(func):
def inner(*args, **kwargs):
try:
ret = func(*args, **kwargs)
report_success(ret)
return ret
except SomeError as error:
report_failure(error)
return
return inner
@report_all
def task_handler():
return = process()
You don't even need task handler at all anymore:
@report_all
def process():
# ...
Upvotes: 2