Sometimes I have a cascade of different things I can try to accomplish a task, e. g. If I need to get a record I can first try to find the record, and if this fails, I can create the missing record, and if this also fails, I can use a tape instead.
Failing is represented by throwing an exception my code needs to catch.
In Python this looks something like this:
try:
record = find_record()
except NoSuchRecord:
try:
record = create_record()
except CreateFailed:
record = tape
This already has the disadvantage of piling-up indentations. If I have five options, this code won't look good.
But I find it even more problematic when there are also else clauses to the try-except clauses:
try:
record = find_record()
except NoSuchRecord:
try:
record = create_record()
except CreateFailed:
record = tape
logger.info("Using a tape now")
else:
logger.info("Created a new record")
else:
logger.info("Record found")
The find_record() and the corresponding Record found message are as far apart as possible which makes it hard to read code. (Moving the code of the else clause directly into the try clause is only an option if this code is definitely not raising one of the exceptions caught in the except statement, so this is no general solution.)
Again, this ugliness gets worse with each added level of nesting.
Is there a nicer way to put this into Python code
without changing the behavior and
while keeping the try and the except clauses of one topic closely together and/or
maybe also avoiding the piling-up nesting and indenting?
You can use a for loop to successively try variants:
for task, error in ((find_record, NoSuchRecord), (create_record, CreateFailed)):
try:
result = task()
except error:
continue
else:
break
else:
# for..else is only entered if there was no break
result = tape
If you need an else clause, you can provide it as a separate function:
for task, error, success in (
(find_record, NoSuchRecord, lambda: logger.info("Record found")),
(create_record, CreateFailed, lambda: logger.info("Created a new record"))
):
try:
result = task()
except error:
continue
else:
success()
break
else:
result = tape
logger.info("Using a tape now")
Take note that the default case tape is not part of the variants - this is because it has no failure condition. If it should execute with the variants, it can be added as (lambda: tape, (), lambda: None).
You can put this all into a function for reuse:
def try_all(*cases):
for task, error, success in cases:
try:
result = task()
except error:
continue
else:
success()
return result
try_all(
(find_record, NoSuchRecord, lambda: logger.info("Record found")),
(create_record, CreateFailed, lambda: logger.info("Created a new record")),
(lambda: tape, (), lambda: logger.info("Using a tape now")),
)
In case the tuples seem difficult to read, a NamedTuple can be used to name the elements. This can be mixed with plain tuples:
from typing import NamedTuple, Callable, Union, Tuple
from functools import partial
class Case(NamedTuple):
task: Callable
error: Union[BaseException, Tuple[BaseException, ...]]
success: Callable
try_all(
Case(
task=find_record,
error=NoSuchRecord,
success=partial(logger.info, "Record found")),
(
create_record, CreateFailed,
partial(logger.info, "Created a new record")),
Case(
task=lambda: tape,
error=(),
success=partial(logger.info, "Using a tape now")),
)
You could break that into multiple functions ?
def handle_missing():
try:
record = create_record()
except CreateFailed:
record = tape
logger.info("Using a tape now")
else:
logger.info("Created a new record")
return record
def get_record():
try:
record = find_record()
except NoSuchRecord:
record = handle_missing()
else:
logger.info("Record found")
return record
And then you'd use it like,
record = get_record()
I think following code is more readable and clean. Also I am sure in real problem we need some parameters to be sent to "find_record" and "create_record" functions like id, some, values to create new record. The factory solution need those parameters also be listed in tuple
def try_create(else_return):
try:
record = create_record()
except CreateFailed:
record = else_return
logger.info("Using a tape now")
else:
logger.info("Created a new record")
def try_find(else_call= try_create, **kwargs):
try:
record = find_record()
except NoSuchRecord:
try_create(**kwargs)
else:
logger.info("Record found")
try_find(else_call=try_create, else_return=tape)
Related
I'm creating a calculator using PLY and I want to be able to stop the parsing when it encounters a division by 0.
If I raise an error, it will stop my program, which is not what I want.
I want to be able to print an error message and then ignore the rest of the parsed string.
Since I'm in a loop, it will end the parsing and ask again for a new input.
while True:
question = input('>>> ')
try:
answer = parser.parse(question)
if answer is not None:
print(answer)
except EOFError:
break
I have a class (not a parsing rule) for handling numbers and operations between them. If a division by zero occurs it will call the error method, which will just print an error message for now.
def error(self, op, other):
print('Illegal operation "{} {} {}" for {}'.format(self, op, other, type(self).__name__))
Which is fine if there is nothing else after the division, but will be a problem for this: 10/0 - 3 + 2 because the parsing is continuing and multiple errors will be thrown. I want to be able to stop the parsing after the very first error.
I was thinking something like lexer.skip(1) but for all the string, not just 1 token. Is there a similar method for this ?
Thanks a lot !
If you just want to interrupt the parse and discard the rest of the line being parsed, raise an exception (or let the Python divide by zero exception through). Here, I've created an exception class for calculator errors, so that bugs in the parser code will still result in backtraces. The code not shown is based on examples/calc/calc.py.
class CalcError(Exception):
def __init__(self, message):
self.message = message
# ...
def p_expression_div(p):
'''expression : expression '/' expression'''
if p[3] == 0:
raise CalcError("Divide by zero")
p[0] = p[1] / p[3]
Then you just need to modify your main loop a bit:
import readline
try:
while True:
question = input('>>> ')
try:
answer = parser.parse(question)
if answer is not None:
print(answer)
except CalcError as e:
print(e.message)
except EOFError:
print()
break
Let's say I have three functions that do different things but should react to a set of exceptions in the same way. One of them might look like:
def get_order_stat(self, Order_id):
status_returned = False
error_count = 0
while status_returned == False:
try:
stat_get = client.queryOrder(orderId=Order_id)
except MalformedRequest:
print('Order ID not yet findable, keep trying')
error_count += 1
time.sleep(int(1))
except InternalError:
print('Order check returned InternalError, keep trying')
error_count += 1
time.sleep(int(1))
except StatusUnknown:
print('Order check returned StatusUnknown, keep trying')
error_count += 1
time.sleep(int(1))
else:
status = stat_get['status']
status_returned = True
finally:
if error_count >= 10:
print('Error loop, give up')
break
return status
The vast majority of the code is the exception handling, and I'd like to avoid having to repeat it in every function that needs it. Is there a way to define something like an exception handling function containing the handling code? Ideally my function would end up effectively:
def get_order_stat(self, Order_id):
status_returned = False
while status_returned == False:
try:
stat_get = client.queryOrder(orderId=Order_id)
except:
handler_function()
else:
status = stat_get['status']
status_returned = True
return status
You practically already did it. Just define the handler_function() somewhere and it gets called when an Exception in the try block gets raised.
Maybe helpful: you can bind the Exception to a variable and use it for exception handling in the handler function:
except Exception as e:
handler_function(e)
Then you can for example do `print(e)̀ to give out the exception or do different handling for different exceptions in the function. Hope that helps!
You also can specify several exceptions in one line if you don't want to be general, but catch all specific exceptions with one statement:
except (ExceptionType1, ExceptionType2, ExceptionType3) as e:
handler_function(e)
I might write a decorator function for the exception handling; for instance using functool.wraps.
from functool import wraps
def retry(f):
#wraps(f)
def wrapper(*args, **kwargs):
error_count = 0
while error_count < 10:
try:
return f(*args, **kwargs)
except MalformedRequest:
print('Order ID not yet findable, keep trying')
except InternalError:
print('Order check returned InternalError, keep trying')
error_count += 1
time.sleep(int(1))
print('Error loop, give up')
return None
return wrapper
Then you can write a very simple API call function, and wrap it with the retry wrapper:
#retry
def get_order(order_id):
stat_get = client.queryOrder(orderId=order_id)
return stat_get['status']
In your original function, notice that you can move the contents of the try...else block into the main try block without affecting the logic (extracting the value from the query result won't raise one of the network-related exceptions), and then you can just return out of the try block instead of arranging to stop the loop. Then the contents of the try block are what I've broken out here into get_order(). I restructured the remaining loop a little and turned it into decorator form.
I have the following query:
profiles = session.query(profile.name).filter(and_(profile.email == email, profile.password == password_hash))
How do I check if there is a row and how do I just return the first (should only be one if there is a match)?
Use query.one() to get one, and exactly one result. In all other cases it will raise an exception you can handle:
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.orm.exc import MultipleResultsFound
try:
user = session.query(User).one()
except MultipleResultsFound, e:
print e
# Deal with it
except NoResultFound, e:
print e
# Deal with that as well
There's also query.first(), which will give you just the first result of possibly many, without raising those exceptions. But since you want to deal with the case of there being no result or more than you thought, query.one() is exactly what you should use.
You can use the first() function on the Query object. This will return the first result, or None if there are no results.
result = session.query(profile.name).filter(...).first()
if not result:
print 'No result found'
Alternatively you can use one(), which will give you the only item, but raise exceptions for a query with zero or multiple results.
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
try:
result = session.query(profile.name).filter(...).one()
print result
except NoResultFound:
print 'No result was found'
except MultipleResultsFound:
print 'Multiple results were found'
Assuming you have a model User, you can get the first result with:
User.query.first()
If the table is empty, it will return None.
Use one_or_none(). Return at most one result or raise an exception.
Returns None if the query selects no rows.
I want to execute several functions, gather their exceptions (if there are any), and raise a compound exception, calling as many of the functions as possible without breaking after one exception. For example, say I have
def f():
do_one()
do_two()
do_three()
The do_i functions don't depend on each other's status. The most obvious way to do what I want is this:
def f():
errors = []
for do_i in [do_one, do_two, do_three]:
try:
do_i()
except Exception as e:
errors.append(e)
if errors:
raise Exception(';'.join(errors))
or slightly better:
def catch_error(arr, f, *args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
arr.append(e)
return None
def f():
errors = []
for do_i in [do_one, do_two, do_three]:
catch_error(errors, do_i)
if errors:
raise Exception(';'.join(errors))
but this is still ugly. Is there a Pythonic way to do this that I'm missing, maybe with clever use of a with statement?
Edit: In a dream world Python would have this:
errors = []
awesome_block(errors):
do_one()
do_two()
do_three()
return 'yes!' if not errors else ';'.join(map(str, errors))
You could rewrite your function into a contextmanager, which does simplify your code a bit. I've maintained your convention of passing a list, though this yields the internal list, so you can use it later.
from contextlib import contextmanager
#contextmanager
def catch_errors(error_list=None):
error_list = error_list if error_list is not None else []
try:
yield error_list
except Exception as e:
error_list.append(e)
error_list = []
with catch_errors(error_list):
raise Exception("First exception")
with catch_errors(error_list):
raise ValueError("Second exception")
if error_list:
raise Exception(";".join(map(repr, error_list)))
I think repr is more useful than str here. #contextmanager allows usage in a with statement while you only have to write the function as a generator.
If you don't pass a list to the generator, then you need to keep track of the returned list.
with catch_errors() as errors1:
raise Exception("First exception")
print errors1 # Exception("First exception",)
I'm trying to extract data from an xml file. A sample of my code is as follows:
from xml.dom import minidom
dom = minidom.parse("algorithms.xml")
...
parameter = dom.getElementsByTagName("Parameters")[0]
# loop over parameters
try:
while True:
parameter_id = parameter.getElementsByTagName("Parameter")[m].getAttribute("Id")
parameter_name = parameter.getElementsByTagName("Name")[m].lastChild.data
...
parameter_default = parameter.getElementsByTagName("Default")[m].lastChild.data
print parameter_id
print parameter_default
m = m+1
except IndexError:
#reached end of available parameters
pass
#except AttributeError:
#parameter doesn't exist
#?
If all elements for each parameter exist, the code runs correctly. Unfortunately the data I am supplied often has missing entries in it, raising an AttributeError exception. If I simply pass on that error, then any elements that do exist but are retrieved later in the loop than when the exception occurred are skipped, which I don't want. I need some way to continue where the code left off and skip to the next line of code if this specific exception is raised.
The only way to work around this that I can think of would be to override the minidom's class methods and catch the exception there, but that seems far too messy and too much work to handle what should be a very simple and common problem. Is there some easier way to handle this that I am missing?
Instead of "an individual try-except block for every statement", why not abstract out that part?
def getParam(p, tagName, index, post=None):
post = post or lambda i: i
try:
return post(p.getElementsByTagName(tagname)[index])
except AttributeError:
print "informative message"
return None # will happen anyway, but why not be explicit?
then in the loop you could have things like:
parameter_id = getParam(parameter, "Parameter", m, lambda x: x.getAttribute("Id"))
parameter_name = getParam(parameter, "Name", m, lambda x: x.lastChild.data)
...
I think there are two parts to your question. First, you want the loop to continue after the first AttributeError. This you do by moving the try and except into the loop.
Something like this:
try:
while True:
try:
parameter_id = parameter.getElementsByTagName("Parameter")[m].getAttribute("Id")
parameter_name = parameter.getElementsByTagName("Name")[m].lastChild.data
...
parameter_default = parameter.getElementsByTagName("Default")[m].lastChild.data
print parameter_id
print parameter_default
m = m+1
except AttributeError:
print "parameter doesn't exist"
#?
except IndexError:
#reached end of available parameters
pass
The second part is more tricky. But it is nicely solved by the other answer.