5

In a Python program I have code with the following structure:

try:
    value = my_function(*args)
finally:
    with some_context_manager:
        do_something()
        if 'value' in locals():
            do_something_else(value)

But the 'value' in locals() construction feels a bit fragile and I am wondering if there is a better way to do this.

What I really want is for the code inside the finally to behave slightly different depending on whether the try block raised an exception. Is there a way to know if an exception was raised?

kasperd
  • 1,952
  • 1
  • 20
  • 31
  • 2
    I think what you want is another context manager, not the try/finally syntax. `__exit__` will know if there was an exception. – timgeb Apr 09 '17 at 15:45
  • 2
    You could also use the `else` block, it runs only when when no exception was raised. – Ashwini Chaudhary Apr 09 '17 at 15:46
  • 1
    @AshwiniChaudhary The `else` block will run before the `finally` block, which changes the semantic of OP's code. – kennytm Apr 09 '17 at 15:49
  • @AshwiniChaudhary But the code I only want to run when there was no exception is inside a context manager invoked by the `finally` block. – kasperd Apr 09 '17 at 16:01
  • Does `do_something` absolutely have to run *after* `my_function`? – chepner Apr 09 '17 at 16:08
  • @chepner Yes. `do_something` must run inside the the context manager and only after `my_function` has returned. The order of `do_something` and `do_something_else` is interchangeable. – kasperd Apr 09 '17 at 16:11

3 Answers3

3

If the goal is "when an exception was raised, do something different", how about:

exception_raised = False
try:
    value = my_function(*args)
except:
    exception_raised = True
    raise
finally:
    with some_context_manager:
        do_something()
        if not exception_raised:
            do_something_else(value)

Now, if you're going to have multiple exceptions that you actually do something with, I'd recommend:

completed_successfully = False
try:
    value = my_function(*args)
else:
    completed_successfully = True
finally:
    with some_context_manager:
        do_something()
        if completed_sucessfully:
            do_something_else(value)
mwchase
  • 811
  • 5
  • 13
1

Here are a couple ideas:

Set value before attempting the try:

value = None
try:
    value = my_function(*args)
finally:
    with some_context_manager:
        do_something()
        if value is not None:
            do_something_else(value)

Or if you want to set the value based on the exception type:

try:
    value = my_function(*args)
except:
    value = None
    raise
finally:
    with some_context_manager:
        do_something()
        if value is not None:
            do_something_else(value)
clutton
  • 620
  • 1
  • 8
  • 18
  • I do not want to catch exceptions raised by `my_function`, but there is cleanup which needs to be done regardless of whether there was an exception or not, which is why I use `finally`. – kasperd Apr 09 '17 at 16:05
  • I have removed the third option that would have caught the exception and adjusted option 2... thanks for clarifying – clutton Apr 09 '17 at 16:13
  • You assume `my_function` will never return `None`, which makes your solution a bit less general than the [answer](http://stackoverflow.com/a/43308834/3476849) by [mwchase](https://stackoverflow.com/users/2117569/mwchase). But yours is slightly shorter, so I'll probably end up using it anyway, since `my_function` happens to only return strings. (I will remove the `'` chars though). – kasperd Apr 09 '17 at 16:20
  • Good point, I agree the answer by mwchase is more general. I would not have written if his was there when I first looked. I missed the ' chars, I'll edit. – clutton Apr 09 '17 at 16:22
-1

Assign the exception to a variable in the except suite then use it in the finally suite.

foo = False
try:
    raise KeyError('foo not found')
except KeyError as e:
    pprint(e)
    foo = e
finally:
    if foo:
        print(foo)
    else:
        print('NO')
wwii
  • 23,232
  • 7
  • 37
  • 77