4

I like to avoid the "look before you leap" paradigm because I value easy-to-read code. In some cases, I cannot predict whether an error will occur, such as resource availability or out-of-memory errors. I haven't found a clean way of writing code that is repetitious or lengthy to handle these scenarios.

The following example is marginally readable, but duplicate code is unacceptable.

try:
    myobject.write(filename)
except OSError:
    if prompt("%s is in use by another application." +
              "Close that application and try again.") == "Try again":
        myobject.write(filename) #repeated code

In order to remove the duplicate code, I must add a few more lines and indent everything, reducing the readability.

success = False
while not success:
    try:
        myobject.write(filename)
        success = True
    except OSError:
        if prompt("%s is in use by another application." +
                  "Close that application and try again.") != "Try again":
            break

Is there a shorter way of writing this in Python that doesn't duplicate code?

IceArdor
  • 1,961
  • 19
  • 20
  • 1
    Have you had a look at this? http://stackoverflow.com/questions/2083987/how-to-retry-after-exception-in-python – Hitesh Dharamdasani Jul 14 '14 at 01:14
  • why are you writing to the file twice? – Padraic Cunningham Jul 14 '14 at 01:15
  • @Padraic In this example, OSError is raised if the file is not written to successfully (another application has exclusive write access to the file) – IceArdor Jul 14 '14 at 04:36
  • @Hitesh thanks for the link. In case others stumble across this article in the future, https://wiki.python.org/moin/PythonDecoratorLibrary#Retry and http://stackoverflow.com/questions/567622/is-there-a-pythonic-way-to-try-something-up-to-a-maximum-number-of-times. Sorry for the duplicate question – IceArdor Jul 14 '14 at 04:40

3 Answers3

6

Aside from switching to while True, you could add a retry decorator, and move your retry-able code to a function decorated by retry:

from functools import wraps
from functools import update_wrapper


def retry(prompt_text="An error occured! Retry (y/n)?", 
          prompt_match='y',
          exception=Exception):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            while True:
                try:
                    ret = func(*args, **kwargs)
                    break
                except exception:
                    if raw_input(prompt_text) == prompt_match:
                        ret = None
                        break
            return ret 
        return update_wrapper(wrapper, func)
    return decorator

@retry(prompt_text="your prompt: ", prompt_match="quit", exception=OSError)
def do_write(myobject, filename):
    myobject.write(filename)

if __name__ == "__main__":
    myobject = ...
    filename = ...
    do_write(myobject, filename) # This will be retried.

It's probably only worth the effort if you're using this pattern in more than one place, though.

Nir Alfasi
  • 53,191
  • 11
  • 86
  • 129
dano
  • 91,354
  • 19
  • 222
  • 219
  • 1
    Haha +1 I came up with the same solution too but you beat me by about 1 minute! Perhaps we could merge features of our answers? See: http://codepad.org/u3BYDSGp – James Mills Jul 14 '14 at 01:27
  • 1
    +1 decorator is the pythonic solution! The only (minor) thing that I would change is to have `wrapper()` accept the text for "prompt" and "try again" to make it more generic and usable with other functions as well. – Nir Alfasi Jul 14 '14 at 01:28
  • 1
    @JamesMills Thanks! I've updated the decorator to incorporate your features, and was inspired added a couple of others as well. – dano Jul 14 '14 at 01:44
  • 1
    Awesome :) I like our combined answer! Too bad we both can't get credit (*reputation*) for it :P – James Mills Jul 14 '14 at 02:05
  • Thanks for the collaborated answer James Mills and dano. I wish SO wasn't so point-centric. The wrapper approach is generic enough for my purposes, reduces duplicated code, and improves readability by separating control flow from end result. Some version of `retry_on_OSError(someobj.write(filename))` might work too. – IceArdor Jul 14 '14 at 04:53
2

Is there a shorter way of writing this in Python that doesn't duplicate code?

Not really. You could maybe use a while True: loop and eliminate the success variable:

while True:
    try:
        myobject.write(filename)
        break
    except OSError:
        if prompt("%s is in use by another application."
                  "Close that application and try again.") != "Try again":
            break

But that is about as compact as you can get while still preserving readability.

Also, you will notice that I removed the + on the if-statement line. It is not necessary because adjacent string literals are automatically concatenated.

  • Thanks. While True and break is marginally shorter. I was hoping for a something even shorter. Moving the retry code off to a decorator seems clearer since it separates the "end result" from the control flow. – IceArdor Jul 14 '14 at 04:47
  • (Even though the total number of lines is greater) – IceArdor Jul 14 '14 at 05:07
1

You can wrap the functions with a decorator class that accepts parameters as input in order to generalize the exception handling:

class exception_handler(object):

    def __init__(self, prompt_text, prompt_match, exception):
        self.prompt = prompt_text
        self.match = prompt_match
        self.exception = exception

    def __call__(self, f):
        def wrapped_f(*args, **kwargs):
            while True:
                try:
                    f(*args, **kwargs)
                    break
                except self.exception:
                    if raw_input(self.prompt) == self.match:
                        break       
        return wrapped_f


@exception_handler("your prompt (type 'quit' to exit): ", "quit", OSError)
def f(filename):
    print("before writing to file: {}".format(filename))
    # myobject.write(filename)
    raise OSError("testing...")
Nir Alfasi
  • 53,191
  • 11
  • 86
  • 129