5

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?

4

3 Answers 3

8

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.

Sign up to request clarification or add additional context in comments.

5 Comments

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: codepad.org/u3BYDSGp
+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.
@JamesMills Thanks! I've updated the decorator to incorporate your features, and was inspired added a couple of others as well.
Awesome :) I like our combined answer! Too bad we both can't get credit (reputation) for it :P
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.
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.

2 Comments

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.
(Even though the total number of lines is greater)
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...")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.