43

Does Python have a finally equivalent for its if/else statements, similar to its try/except/finally statements? Something that would allow us to simplify this:

 if condition1:
      do stuff
      clean up
 elif condition2:
      do stuff
      clean up
 elif condition3:
      do stuff
      clean up
 ...
 ...

to this:

 if condition1:
      do stuff
 elif condition2:
      do stuff
 elif condition3:
      do stuff
 ...
 ...
 finally:
      clean up

Where finally would only be called only after a condition was met and its 'do stuff' run? Conversely, if no condition was met, the finally code would not be run.

I hate to spout blasphemy, but the best way I can describe it is there being a GOTO statement at the end of each block of 'do stuff' that led to finally.

Essentially, it works as the opposite of an else statement. While else is only run if no other conditions are met, this would be ran ONLY IF another condition was met.

nobillygreen
  • 1,548
  • 5
  • 19
  • 27

9 Answers9

41

It can be done totally non-hackily like this:

def function(x,y,z):
    if condition1:
        blah
    elif condition2:
        blah2
    else:
        return False

    #finally!
    clean up stuff.

In some ways, not as convenient, as you have to use a separate function. However, good practice to not make too long functions anyway. Separating your logic into small easily readable (usually maximum 1 page long) functions makes testing, documenting, and understanding the flow of execution a lot easier.

One thing to be aware of is that the finally clause will not get run in event of an exception. To do that as well, you need to add try: stuff in there as well.

Daniel Fairhead
  • 1,103
  • 9
  • 9
  • 24
    The `finally` clause won't run either if `else: return False` is met. – RolfBly Aug 05 '14 at 10:37
  • 2
    This construct doesn't work if the if/else/finally is used to break from an outer loop. – Hyperplane Jul 03 '19 at 10:53
  • 4
    @RolfBly that's the purpose of the code: if you want to run the "finally" block only if one of the conditions is met, you cannot include the "else" block in the condition, otherwise the finally block will always run. – Vincenzooo Jan 20 '20 at 13:00
  • @Vincenzoo Yup, I know. Perhaps I should have suggested to incorporate this in the explanation. – RolfBly Jan 21 '20 at 16:23
13

you can use try

try:
    if-elif-else stuff
finally:
    cleanup stuff

the exception is raised but the cleanup is done

Durgeoble
  • 231
  • 2
  • 5
10

Your logic is akin to this:

cleanup = True
if condition1:
    do stuff
elif condition2:
    do stuff
elif condition3:
    do stuff
....
else:
    cleanup = False

if cleanup:
    do the cleanup

Ugly, but it is what you asked

Ricardo Cárdenes
  • 9,004
  • 1
  • 21
  • 34
  • See, I had exactly this in my own code, and I agree, it is VERY ugly. I was surprised when I went looking that there really is no good way to beautify this. – nobillygreen Feb 06 '14 at 20:14
  • 1
    It is, but maybe you should rethink your program's logic, because `if/elif/else` is just not supposed to follow that kind of construct – Ricardo Cárdenes Feb 06 '14 at 20:18
6

A little late to the party, but see the question has been active recently.

Usually I would make a context manager like this

class CleanUp(object):

    class Cancel(Exception):
        pass

    def __init__(self, f_cleanup):
        self.f_cleanup = f_cleanup

    def __enter__(self):
        return self

    def __exit__(self, exception_type, exception_value, traceback):

        cancelled = exception_type and issubclass(exception_type, self.__class__.Cancel)

        if not cancelled:
            self.f_cleanup()

        return not exception_type or cancelled

    def cancel(self):
        raise self.__class__.Cancel

And then you can use it like this

def cleanup():
    print "Doing housekeeping"


with CleanUp(cleanup) as manager:
    if condition1:
        do stuff
    elif condition2:
        do stuff
    else:
        manager.cancel()
Claus Nielsen
  • 476
  • 6
  • 11
  • Oh I like this one. Very DRY. – DylanYoung Mar 22 '19 at 14:02
  • 2
    @DylanYoung Ye, I usually keep this around as I find that this task is more common than one would think. Just keep in mind that this example does the clean up unless cancel is called, meaning also on any kind of exception. – Claus Nielsen Mar 25 '19 at 20:48
5

The answer of mhlester has repetitive code, an improved version might be as follows:

class NoCleanUp(Exception):
    pass

try:
    if condition1:
        do stuff
    elif condition2:
        do stuff
    else:
        raise NoCleanUp
except NoCleanUp:
    pass
else:
    cleanup
JLT
  • 712
  • 9
  • 15
1

Another suggestion, which might suit you if the conditions are pre-computed.

if condition1:
    do stuff
elif condition2:
    do stuff
...
if any([condition1, condition2, ...]):
    clean_up

This would be a pain if you were evaluating the conditions as part of your if statements, because in that case you would have to evaluate them a second time for the any function...unless Python is smarter than I realise.

Pig
  • 485
  • 4
  • 14
0

Is this hideous?

for _ in range(1):
    if condition1:
        do stuff
        break
    elif condition2:
        do stuff
        break
else:
    finally stuff

How about this?

class NoFinally(Exception):
    pass

try:
    if condition1:
        do stuff
        raise NoFinally
    elif condition2:
        do stuff
        raise NoFinally
except NoFinally:
    pass
else:
    finally

Honestly, I hate both of these

mhlester
  • 22,781
  • 10
  • 52
  • 75
0

Like this:

from .actions import stuff1, stuff2

actions={condition1: stuff1, condition2: stuff2}
for condition in actions:
    if condition:
        actions[condition]()
        cleanup()
        break

Caveats are of course that your condition keys have to be unique and hashable. You can get around this by using a different data structure.

DylanYoung
  • 2,423
  • 27
  • 30
-1

I gotchu:

def finally(self):
    pass

if 2 > 1:
    print('over')
elif 2 < 1:
    print('under')
self.finally()
print('done')
  • I think you've missed the point which is that finally() should only run if one of the conditions is true, whereas for your code finally() always runs – kevinlinxc Nov 18 '22 at 18:35