A DRY approach to Python try-except blocks? - python

Objective: I have several lines of code each capable of producing the same type of error, and warranting the same kind of response. How do I prevent a 'do not repeat yourself' problem with the try-except blocks.
Background:
I using ReGex to scrape poorly formatted data from a text file, and input it into the field of a custom object. The code works great except when the field has been left blank in which case it throws an error.
I handle this error in a try-except block. If error, insert a blank into the field of the object (i.e. '').
The problem is it turns easily readable, nice, Python code into a mess of try-except blocks that each do exact same thing. This is a 'do not repeat yourself' (a.k.a. DRY) violation.
The Code:
Before:
sample.thickness = find_field('Thickness', sample_datum)[0]
sample.max_tension = find_field('Maximum Load', sample_datum)[0]
sample.max_length = find_field('Maximum Extension', sample_datum)[0]
sample.test_type = sample_test
After:
try:
sample.thickness = find_field('Thickness', sample_datum)[0]
except:
sample.thickness = ''
try:
sample.max_tension = find_field('Maximum Load', sample_datum)[0]
except:
sample.max_tension = ''
try:
sample.max_length = find_field('Maximum Extension', sample_datum)[0]
except:
sample.max_length = ''
try:
sample.test_type = sample_test
except:
sample.test_type = ''
What I Need:
Is there some Pythonic way to write this? Some block where I can say if there is an index-out-of-range error on any of these lines (indicating the field was blank, and ReGex failed to return anything) insert a blank in the sample field.

What about extracting a function out of it?
def maybe_find_field(name, datum):
try:
return find_field(name, datum)[0]
except IndexError: # Example of specific exception to catch
return ''
sample.thickness = maybe_find_field('Thickness', sample_datum)
sample.max_tension = maybe_find_field('Maximum Load', sample_datum)
sample.max_length = maybe_find_field('Maximum Extension', sample_datum)
sample.test_type = sample_test
BTW, don't simply catch all possible exceptions with except: unless that's really what you want to do. Catching everything may hide implementation errors and become quite difficult to debug later. Whenever possible, bind your except case to the specific exception that you need.

Not quite matching the question, but Google sent me here and so others might come.
I have post functions in two separate Django-views, which call similar backend-functions and require the same exception-handling.
I solved the issue by extracting the whole except:-tree and sticking it into a decorator.
Before:
# twice this
def post(request):
try:
return backend_function(request.post)
except ProblemA as e:
return Response("Problem A has occurred, try this to fix it.", status=400)
except ProblemB as e:
return Response("Problem B has occurred, try this to fix it.", status=400)
except ProblemC as e:
return Response("Problem C has occurred, try this to fix it.", status=400)
except ProblemD as e:
return Response("Problem D has occurred, try this to fix it.", status=400)
After:
# once this
def catch_all_those_pesky_exceptions(original_function):
def decorated(*args, **kwargs):
try:
return original_function(*args, **kwargs)
except ProblemA as e:
return Response("Problem A has occurred, try this to fix it.", status=400)
except ProblemB as e:
return Response("Problem B has occurred, try this to fix it.", status=400)
except ProblemC as e:
return Response("Problem C has occurred, try this to fix it.", status=400)
except ProblemD as e:
return Response("Problem D has occurred, try this to fix it.", status=400)
return decorated
# twice this - note the #decorator matching the above function.
#catch_all_those_pesky_exceptions
def post(request):
return backend_function(request.post)
Now I can add more exception-handling in a single place.

When you find yourself repeating code, encapsulate it in a function. In this case, create a function that handles the exception for you.
def try_find_field(field_name, datum, default_value):
try:
return find_field(field_name, datum)[0]
except:
return default_value

What about something like this:
def exception_handler(exception_class):
logger = logging.getLogger('app_error')
logger.error(exception_class)
exception_name = exception_class.__name__
if exception_name == 'AuthenticationError':
raise AuthenticationError
elif exception_name == 'AuthorizationError':
raise AuthorizationError
elif exception_name == 'ConnectionError':
raise ConnectionError
else:
raise GenericError
def call_external_api()
try:
result = http_request()
except Exception as e:
exception_handler(exception_class=type(e))

You can have any number of except blocks over and over, handling different kinds of exceptions. There's also nothing wrong with having multiple statements in the same try/catch block.
try:
doMyDangerousThing()
except ValueError:
print "ValueError!"
except HurrDurrError:
print "hurr durr, there's an error"
try:
doMyDangerousThing()
doMySecondDangerousThing()
except:
print "Something went wrong!"

Related

How do i return error in exception in python?

I want to return the error in my code that I wrote in python. I can't do this. How can I do it?
def proc():
try:
a=2/0
except Exception as e:
print("Except")
raise f"{e}"
else:
return "Success"
result=proc()
print("result : ",result)
I tried using direct raise but it didn't work? How can I do?
If you just want to return the error message with the class name, you could probably do this:
def proc():
try:
a=2/0
except Exception as e:
print("Except")
return repr(e) # Repr is a great solution
else:
return "Success"
result=proc()
print("result : ",result)
Result:
Except
result : ZeroDivisionError(division by zero)

More pythonic way to handle nested try... except blocks?

Is there a cleaner or more pythonic way to do the following?
try:
error_prone_function(arg1)
except MyError:
try:
error_prone_function(arg2)
except MyError:
try:
another_error_prone_function(arg3)
except MyError:
try:
last_error_prone_function(arg4)
except MyError:
raise MyError("All backup parameters failed.")
Basically it's: If attempt #1 fails, try #2. If #2 fails, try #3. If #3 fails, try #4. If #4 fails, ... if #n fails, then finally raise some exception.
Note that I'm not necessarily calling the same function every time, nor am I using the same function arguments every time. I am, however, expecting the same exception MyError on each function.
Thanks to John Kugelman's post here, I decided to go with this which utilizes the lesser-known else clause of a for loop to execute code when an entire list has been exhausted without a break happening.
funcs_and_args = [(func1, "150mm"),
(func1, "100mm",
(func2, "50mm"),
(func3, "50mm"),
]
for func, arg in funcs_and_args :
try:
func(arg)
# exit the loop on success
break
except MyError:
# repeat the loop on failure
continue
else:
# List exhausted without break, so there must have always been an Error
raise MyError("Error text")
As Daniel Roseman commented below, be careful with indentation since the try statement also has an else clause.
A generator based approach might give you a little more flexibility than the data-driven approach:
def attempts_generator():
# try:
# <the code you're attempting to run>
# except Exception as e:
# # failure
# yield e.message
# else:
# # success
# return
try:
print 'Attempt 1'
raise Exception('Failed attempt 1')
except Exception as e:
yield e.message
else:
return
try:
print 'Attempt 2'
# raise Exception('Failed attempt 2')
except Exception as e:
yield e.message
else:
return
try:
print 'Attempt 3'
raise Exception('Failed attempt 3')
except Exception as e:
yield e.message
else:
return
try:
print 'Attempt 4'
raise Exception('Failed attempt 4')
except Exception as e:
yield e.message
else:
return
raise Exception('All attempts failed!')
attempts = attempts_generator()
for attempt in attempts:
print attempt + ', retrying...'
print 'All good!'
The idea is to build a generator that steps through attempt blocks via a retry loop.
Once the generator hits a successful attempt it stops its own iteration with a hard return. Unsuccessful attempts yield to the retry loop for the next fallback. Otherwise if it runs out of attempts it eventually throws an error that it couldn't recover.
The advantage here is that the contents of the try..excepts can be whatever you want, not just individual function calls if that's especially awkward for whatever reason. The generator function can also be defined within a closure.
As I did here, the yield can pass back information for logging as well.
Output of above, btw, noting that I let attempt 2 succeed as written:
mbp:scratch geo$ python ./fallback.py
Attempt 1
Failed attempt 1, retrying...
Attempt 2
All good!
If you uncomment the raise in attempt 2 so they all fail you get:
mbp:scratch geo$ python ./fallback.py
Attempt 1
Failed attempt 1, retrying...
Attempt 2
Failed attempt 2, retrying...
Attempt 3
Failed attempt 3, retrying...
Attempt 4
Failed attempt 4, retrying...
Traceback (most recent call last):
File "./fallback.py", line 47, in <module>
for attempt in attempts:
File "./fallback.py", line 44, in attempts_generator
raise Exception('All attempts failed!')
Exception: All attempts failed!
Edit:
In terms of your pseudocode, this looks like:
def attempts_generator():
try:
error_prone_function(arg1)
except MyError
yield
else:
return
try:
error_prone_function(arg2)
except MyError
yield
else:
return
try:
another_error_prone_function(arg3)
except MyError:
yield
else:
return
try:
last_error_prone_function(arg4)
except MyError:
yield
else:
return
raise MyError("All backup parameters failed.")
attempts = attempts_generator()
for attempt in attempts:
pass
It'll let any exception but MyError bubble out and stop the whole thing. You also could choose to catch different errors for each block.

Catch exception and continue try block in Python

Can I return to executing the try block after exception occurs?
For example:
try:
do_smth1()
except:
pass
try:
do_smth2()
except:
pass
vs.
try:
do_smth1()
do_smth2()
except:
??? # magic word to proceed to do_smth2() if there was exception in do_smth1
No, you cannot do that. That's just the way Python has its syntax. Once you exit a try-block because of an exception, there is no way back in.
What about a for-loop though?
funcs = do_smth1, do_smth2
for func in funcs:
try:
func()
except Exception:
pass # or you could use 'continue'
Note however that it is considered a bad practice to have a bare except. You should catch for a specific exception instead. I captured for Exception because that's as good as I can do without knowing what exceptions the methods might throw.
While the other answers and the accepted one are correct and should be followed in real code, just for completeness and humor, you can try the fuckitpy ( https://github.com/ajalt/fuckitpy ) module.
Your code can be changed to the following:
#fuckitpy
def myfunc():
do_smth1()
do_smth2()
Then calling myfunc() would call do_smth2() even if there is an exception in do_smth1())
Note: Please do not try it in any real code, it is blasphemy
You can achieve what you want, but with a different syntax. You can use a "finally" block after the try/except. Doing this way, python will execute the block of code regardless the exception was thrown, or not.
Like this:
try:
do_smth1()
except:
pass
finally:
do_smth2()
But, if you want to execute do_smth2() only if the exception was not thrown, use a "else" block:
try:
do_smth1()
except:
pass
else:
do_smth2()
You can mix them too, in a try/except/else/finally clause.
Have fun!
'continue' is allowed within an 'except' or 'finally' only if the try block is in a loop. 'continue' will cause the next iteration of the loop to start.
So you can try put your two or more functions in a list and use loop to call your function.
Like this:
funcs = [f,g]
for func in funcs:
try: func()
except: continue
For full information you can go to this link
You could iterate through your methods...
for m in [do_smth1, do_smth2]:
try:
m()
except:
pass
one way you could handle this is with a generator. Instead of calling the function, yield it; then whatever is consuming the generator can send the result of calling it back into the generator, or a sentinel if the generator failed: The trampoline that accomplishes the above might look like so:
def consume_exceptions(gen):
action = next(gen)
while True:
try:
result = action()
except Exception:
# if the action fails, send a sentinel
result = None
try:
action = gen.send(result)
except StopIteration:
# if the generator is all used up, result is the return value.
return result
a generator that would be compatible with this would look like this:
def do_smth1():
1 / 0
def do_smth2():
print "YAY"
def do_many_things():
a = yield do_smth1
b = yield do_smth2
yield "Done"
>>> consume_exceptions(do_many_things())
YAY
Note that do_many_things() does not call do_smth*, it just yields them, and consume_exceptions calls them on its behalf
I don't think you want to do this. The correct way to use a try statement in general is as precisely as possible. I think it would be better to do:
try:
do_smth1()
except Stmnh1Exception:
# handle Stmnh1Exception
try:
do_smth2()
except Stmnh2Exception:
# handle Stmnh2Exception
Depending on where and how often you need to do this, you could also write a function that does it for you:
def live_dangerously(fn, *args, **kw):
try:
return fn(*args, **kw)
except Exception:
pass
live_dangerously(do_smth1)
live_dangerously(do_smth2)
But as other answers have noted, having a null except is generally a sign something else is wrong with your code.
This can be done with exec() in a custom function, a list of strings, and a for loop.
The function with exec():
def try_it(string):
try:
exec(string)
print(f'Done: {string}')
except:
print(f'Error. Could not do: {string}')
More on exec():
exec(object)
This function supports dynamic execution of Python code. object must be either a string or a code object.
Example list of strings and for loop:
do_smth_list = ['do_smth1()', 'do_smth2()', 'do_smth3()']
for smth in do_smth_list:
try_it(smth)
This definitely isn't the cleanest way of doing it, but you can put it in a while loop with a variable set to true, and when it runs the function successfully it sets the variable to false, whereas if it fails it keeps the variable set to true.
x = True
while x == True:
try:
do_smth1()
do_smth2()
x = False
except Exception:
x = True
This way what happens is that the while loop will keep on looping the try except section again and again until it works, in which x is set to false and the loop stops
Also, you can implement a break in the while loop instead of basing it on a variable, for example:
while True:
try:
do_smth1()
do_smth2()
break
except Excpetion:
pass
P.S It is good ettiquete to put a specific exception for the except section, instead of leaving it for any exception. It makes the code cleaner and is more sensible when managing errors especially in bigger projects
special_func to avoid try-except repetition:
def special_func(test_case_dict):
final_dict = {}
exception_dict = {}
def try_except_avoider(test_case_dict):
try:
for k,v in test_case_dict.items():
final_dict[k]=eval(v) #If no exception evaluate the function and add it to final_dict
except Exception as e:
exception_dict[k]=e #extract exception
test_case_dict.pop(k)
try_except_avoider(test_case_dict) #recursive function to handle remaining functions
finally: #cleanup
final_dict.update(exception_dict)
return final_dict #combine exception dict and final dict
return try_except_avoider(test_case_dict)
Run code:
def add(a,b):
return (a+b)
def sub(a,b):
return (a-b)
def mul(a,b):
return (a*b)
case = {"AddFunc":"add(8,8)","SubFunc":"sub(p,5)","MulFunc":"mul(9,6)"}
solution = special_func(case)
Output looks like:
{'AddFunc': 16, 'MulFunc': 54, 'SubFunc': NameError("name 'p' is not defined")}
To convert to variables:
locals().update(solution)
Variables would look like:
AddFunc = 16, MulFunc = 54, SubFunc = NameError("name 'p' is not defined")
If do_smth1() worked, then do_smth2() will not be tried.
try:
x=do_smth1()
except:
try:
x=do_smth2()
except:
x="Both Failed"
print (x)

Is "return False" needed after "traceback.print_exc()"?

My code about this:
try:
self.cookie = Cookie.SimpleCookie(os.environ["HTTP_COOKIE"])
tmpuid = self.cookie["uid"].value
tmpsid = self.cookie["sid"].value
except Exception as e:
if not str(e).startswith('No cookie set'):
import traceback
traceback.print_exc()
return False
Is "return False" needed after "traceback.print_exc()"?
Sure, The exception only stops on No cookie set exceptions. If it is any other exception the program will return False instead of continued to the next statement

How do I return an exception?

I wrote a function that needs to do 3 checks and if one of the tests fails it should return an exception of type of LookupError, but it doesn't work.
(*verify_checksum is another function)
def check_datagram(datagram, src_comp, dst_app):
try:
src_comp==datagram[0:16]
except LookupError:
return "Mismatch in src_comp"
try:
dst_app==datagram[40:48]
except LookupError:
return "Mismatch in dst_app"
try:
verify_checksum(datagram)
except False:
return "Wrong checksum"
return True
For example:
Input:
check_datagram("1111000000001111000011111111000001010101101010101111111111111111000000001111111100000000","0000111100001111", "11110000")
Expected output:
"Mismatch in dst_app"
def check_datagram(datagram, src_comp, dst_app):
if src_comp != datagram[0:16]:
raise LookupError("Mismatch in src_comp")
if dst_app != datagram[40:48]:
raise LookupError("Mismatch in dst_app")
if not verify_checksum(datagram):
raise LookupError("Wrong checksum")
return True # redundant?
With construction from NPE's answer you should use try..except there where you'll use declared check_datagram() function.
#python3
try:
check_datagram(a,b,c)
except LookupError as e:
print(str(e))
That allow you to get message from raised error.

Categories