Python: How to simplify multiple statements with the same try/except block - python

I have a code block like this:
try:
gitlab.gl_delete_project(gitlab_project_id)
except DevOpsError as e:
if e.status_code != 404:
raise e
try:
redmine.rm_delete_project(redmine_project_id)
except DevOpsError as e:
if e.status_code != 404:
raise e
try:
if harbor_project_id is not None:
harbor.hb_delete_project(harbor_project_id)
except DevOpsError as e:
if e.status_code != 404:
raise e
Each method may raise a DevOpsError, and in some conditions, I just want to ignore the Exception, or re-raise it in other times.
Since the try/except block are all identical, is there a way to simplify the code?
Edit: I'm suggested a similar question, but it does not tell me how to pass arguments to the methods.

Define function and call it as many time as needed:
def try_to_delete(delete_method, object):
try:
delete_method(object)
except DevOpsError as e:
if e.status_code != 404:
raise e
try_to_delete(gitlab.gl_delete_project, gitlab_project_id)
try_to_delete(redmine.rm_delete_project, redmine_project_id)
if harbor_project_id is not None:
try_to_delete(harbor.hb_delete_project, harbor_project_id)

Related

Raising multiple exception in python

Is there any way to raise multiple exceptions in python? In the following example, only the first exception is raised.
l = [0]
try:
1 / 0
except ZeroDivisionError as e:
raise Exception('zero division error') from e
try:
l[1]
except IndexError as e:
raise Exception('Index out of range') from e
Is there any other way?
Once an exception is raised and not catched, the execution of your program will stop. Hence, only one exception can be launched in such a simple script.
If you really want your program to raise multiple exceptions, you could however create a custom Exception representing multiple exceptions and launch it inside an except block. Example:
class ZeroDivAndIndexException(Exception):
"""Exception for Zero division and Index Out Of Bounds."""
I = [0]
try:
1 / I[0]
except ZeroDivisionError as e:
try:
I[1]
# Here you may raise Exception('zero division error')
except IndexError as e2:
raise ZeroDivAndIndexException()
Here my solution a bit long but seems to work :
class CustomError(Exception):
pass
l = [0]
exeps = []
try:
1 / 0
except ZeroDivisionError as e:
exeps.append(e)
try:
l[1]
except IndexError as e:
exeps.append(e)
if len(exeps)!=0:
[print(i.args[0]) for i in exeps]
raise CustomError("Multiple errors !!")

Why a multiple inherited Exception does not catch the parent exception?

I'm assuming that following code suppose to print ("CustomExceptionALL"), but that never happens if we raise CustomException1, CustomException2 or CustomException3 while CustomExceptionALL works. Why except CustomExceptionALL doesn't catch CustomException3?
class CustomException1(Exception):
pass
class CustomException2(Exception):
pass
class CustomException3(Exception):
pass
class CustomExceptionALL(CustomException1, CustomException2, CustomException3):
pass
try:
raise CustomException3
except CustomExceptionALL as e:
print("CustomExceptionALL")
except Exception as e:
print(e)
The use case is more the other way round: you raise the derived exception and then catch it using the parent class. For example:
class Brexit(Exception):
pass
class Covid(Exception):
pass
class DoubleWhammy(Brexit, Covid):
pass
try:
raise DoubleWhammy
except Brexit as e:
print("Brexit")
except Exception as e:
print(e)
Because you can only catch subclasses of the specified exception. In your case, these two are false:
isinstance(CustomException3(), CustomExceptionALL) # False
issubclass(CustomException3, CustomExceptionALL) # False
(Because you are trying to catch a CustomExceptionALL, but a CustomException3 is not a CustomExceptionALL, but the other way around)
You can instead use a tuple of classes:
CustomExceptionALL = (CustomException1, CustomException2, CustomException3)
isinstance(CustomException3(), CustomExceptionALL) # True
issubclass(CustomException3, CustomExceptionALL) # True
try:
raise CustomException3
except CustomExceptionALL as e:
print("CustomExceptionALL") # This prints
except Exception as e:
print(e)

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.

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.

Nested exceptions?

Will this work?
try:
try:
field.value = filter(field.value, fields=self.fields, form=self, field=field)
except TypeError:
field.value = filter(field.value)
except ValidationError, e:
field.errors += e.args
field.value = revert
valid = False
break
Namely, if that first line throws a ValidationError, will the second except catch it?
I would have written it un-nested, but the second filter statement can fail too! And I want to use the same ValidationError block to catch that as well.
I'd test it myself, but this code is so interwoven now it's difficult to trip it properly :)
As a side note, is it bad to rely on it catching the TypeError and passing in only one arg instead? i.e., deliberately omitting some arguments where they aren't needed?
If the filter statement in the inner try raises an exception, it will first get checked against the inner set of "except" statements and then if none of those catch it, it will be checked against the outer set of "except" statements.
You can convince yourself this is the case just by doing something simple like this (this will only print "Caught the value error"):
try:
try:
raise ValueError('1')
except TypeError:
print 'Caught the type error'
except ValueError:
print 'Caught the value error!'
As another example, this one should print "Caught the inner ValueError" only:
try:
try:
raise ValueError('1')
except TypeError:
pass
except ValueError:
print 'Caught the inner ValueError!'
except ValueError:
print 'Caught the outer value error!'
To compliment Brent's answer, and test the other case:
class ValidationError(Exception): pass
def f(a): raise ValidationError()
try:
try:
f()
except TypeError:
f(1)
except ValidationError:
print 'caught1'
try:
try:
f(1)
except TypeError:
print 'oops'
except ValidationError:
print 'caught2'
Which prints:
caught1
caught2

Categories