1

I have a bunch of methods which are quite simple to write, but which all require handling exceptions. Exception management is always the same, so I would like to write a wrapper as a function, to be called with a specific function call instance as parameter. Is it possible?

Something along these lines (this is a non-working illustration):

def g(h):
    while True:
        try:
            x = h()
        except:
            # got an error
            print ("Unexpected error. Retry.")
        else:
            # everything is fine
            return x

def f1(x):
    # do something that may fail (e.g. access a distant server)
    y = (...whatever...) 
    return y

def f2(x,y):
    # do something else that may fail (e.g. access a distant server, but different request)
    z = (...whatever...) 
    return z

print (g(f1(3)))
print (g(f1(6)))
print (g(f2(5,'abc')))

Note: I am looking for an answer which does not require class definition. Also, I am not familiar with lambda function in python, but could it be part of the solution?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
nekonaute
  • 31
  • 6
  • g wont return until succeeds - thats what you want? – Patrick Artner Dec 31 '17 at 15:58
  • Yes, exactly. I should have mentioned it. In practical, I implement a timeout in terms of max number of trials. – nekonaute Dec 31 '17 at 16:09
  • from your code - why does it not work? havent posted it in pyfiddle yet, but it seems legit ... - you might need to theese to `g`: [`*args` and `**kwargs`](https://stackoverflow.com/questions/36901/what-does-double-star-asterisk-and-star-asterisk-do-for-parameters) to allow `g` to take the additional parameters that are wanted for your funtions - kindof like `g(f1,3)` and `g` needs to pass on stuff to the given function.... – Patrick Artner Dec 31 '17 at 16:19
  • 1
    there is also the concept of "decorators" which I have not fully groked. might help you out though: https://stackoverflow.com/questions/739654/how-to-make-a-chain-of-function-decorators - as in `adding a "retry n times" decorator` to your functions – Patrick Artner Dec 31 '17 at 16:22

2 Answers2

1

You need to return functions from your first function.

def g(h):
    def f(*arguments):
        try:
            x = h(*arguments)
        except:
            # got an error
            print ("Unexpected error. Retry.")
        else:
            # everything is fine
            return x
    return f

I am using the * operators to allow for variable number of arguments: https://docs.python.org/3/tutorial/controlflow.html#arbitrary-argument-lists

Each time you call g with a function h, it will return you a function that when call with some arguments, will call h with those same arguments (but inside of error handling).

You can now use it with:

g(f1)(3)

Alternatively, you could use directly the * operator in the g method, to avoid creating a new function.

def g(h, *arguments):
    try:
        x = h(*arguments)
    except:
        # got an error
        print ("Unexpected error. Retry.")
    else:
        # everything is fine
        return x

You then would call it like that:

g(f1, 3, 4)

I find the first alternative cleaner especially because you can have safe functions, that you will manipulate:

safe_f1 = g(f1)
safe_f1(3)
Axnyff
  • 9,213
  • 4
  • 33
  • 37
1

This might help:

Using decorators

def exception_handler_wrapper(fn):
    def new_fn(*args, **kwargs):
        try:
            result = fn(*args, **kwargs)
        except Exception as e:
            # return error string or print error string or log error string
            # or do whatever suits you, with the exception
            return "Error occured:" + str(e)
        else:
            return result
    return new_fn


@exception_handler_wrapper
def f1(x):
    # do something that may fail (e.g. access a distant server)
    # emulating something that may fail:
    if x % 2 == 0:
        return "success"
    else:
        raise ValueError("x not even")


@exception_handler_wrapper
def f2(x, y):
    # do something else that may fail (e.g. access a distant server, but
    # different request)
    # emulating something that may fail:
    if x != 0 or y != 'abc':
        return "success"
    else:
        raise Exception("Sum not 100")

print("Expression f1(3):", f1(3))
print("Expression f1(6):", f1(6))
print("Expression f2(5, 'abc'):", f2(5, 'abc'))

and the output was:

Expression f1(3): Error happend:x not even
Expression f1(6): success
Expression f2(5, 'abc'): success

Now with any function whose exception you want to handle, just place @exception_handler_wrapper before it's definition and you can just call that function normally instead of calling it something like: g(f1(6)).

tkhurana96
  • 919
  • 7
  • 25