wallace
wallace

Reputation: 307

Pythonic way of "flags" concept

Consider the following situation:

flag = 'a'
while True:
    try:
        # something that might fail
    except:
        # fix failure
        flag = 'b'
    else:
        break

print(flag)

I feel like there must be a better way to find out what happened without using flags in this case, but can't think of anything that's more "pythonic".

Upvotes: 1

Views: 185

Answers (3)

Kenny Ostrom
Kenny Ostrom

Reputation: 5871

I think this is more of a general programming question. Use good names that mean what you mean. What does "flag" mean? Rename it to "done" and reread the code, and you see a bunch of unnecessary stuff for a simple while loop.

numerator = 1
denominator = 0

done = False
while not done:
    try:
        x = numerator // denominator
        done = True
    except:
        denominator = 1

But to have a good accounting of what happened, and why, we want to add logging (also general programming, not specifically pythonic). This will allow you to keep track of many different code paths in many different exception handlers, for example.

import logging

# one time setup, can be in a different file
log = logging.getLogger('myapp')
log.setLevel(logging.ERROR)
fh = logging.FileHandler('myapp.log')
log.addHandler(fh)

log.info('about to begin')
done = False
while not done:
    try:
        x = n // d
        done = True
    except NameError as e:
        log.error('oops forgot to initialize: ' + str(e))
        n = 1
        d = 0
    except ZeroDivisionError as e:
        log.error('division by zero')
        d = 1
    except Exception as e:
        log.error('unknown error: ' + str(e))

log.info('done')

The errors will be stored in the log file you specified. You have a lot of control over whether or not it logs, and where it puts the logs. For example, I have log.info messages for begin and end, but you don't always want everything. They do not log because I set the log level to ERROR, so this log file 'myapp.log' will only have the log.error messages.

Think of logging like a print statement that only goes where you want it to go, if you actually want to know about it, so you can efficiently leave it in there. logging.DEBUG messages are especially good for knowing when stuff happened in the program as you debug it, but are disabled later by setting a different logging level.

Upvotes: 2

urban
urban

Reputation: 5682

There is another pattern that I like:

  1. Wrap the unsafe function in a safe one that will return False or None on failure
  2. Set your initial values
  3. Do a while <failing>

Example:

# It only works for positive numbers
In [24]: def run_safe(x):
    ...:     if x < 0:
    ...:         x = 0
    ...:     try:
    ...:         return 100 / x
    ...:     except Exception:
    ...:         return None


In [25]: i = -4

In [26]: while run_safe(i) is None:
    ...:     print("i={} failed".format(i))
    ...:     i += 1
    ...:
    ...:
i=-4 failed
i=-3 failed
i=-2 failed
i=-1 failed
i=0 failed

# At the end i has the value that succeeds
In [27]: print(i)
1

For me, this method separates the risky code (which lives in the function now) from the "fix" which lives in the while loop. I find this a lot easier to read when either of the codes (risky or fix) are complex.

Upvotes: 1

Nathan
Nathan

Reputation: 10306

You could pull the handling for the exception / default cases into the except and else blocks instead. This is different code, however, because now you'll be printing on every exception instead of only once, after exiting the while loop, so this would make more sense for a try/except/else outside of a loop. If you're inside the loop but still only want to print once when the loop is done (i.e. once no exception is raised), I think your method is clear enough.

while True:
    try:
        # something that might fail
    except:
        # fix failure
        print("b")
    else:
        print("a")
        break

Upvotes: 2

Related Questions