Question

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?

Was it helpful?

Solution

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():
    # ...
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top