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.
Related
I'm trying to create a class "Hippocrates" with a function inside that lets me create a dictionary from a given text document (the content of the document is not important to my issue). When I try to return a value from the dictionary and the key does not exist in said dictionary, I want to raise a ValueError stating "invalid ICD-code". I then want the code to continue running since I need it to be able to keep returning values one after each other, but since I raise a ValueError the code stops.
I tried putting it inside a try except block but I'm not too familiar with it yet so I'm struggling.
Here's what I have so far:
class Hippocrates:
def __init__(self, location):
self.file = open(location, "r")
def description(self, code):
answer = {}
data = self.file.readline()
while data:
current = data.split()
x = current.pop(0)
current = ' '.join(current)
answer[x] = answer.get(current, current)
data = self.file.readline()
try:
return answer[code]
except ValueError:
print('invalid ICD-code')
When I try getting a value from it with an invalid key I get a KeyError. I don't know why this would happen since it should just go straight to a ValueError.
I do get the correct value when I use a valid key.
Can someone help me figure this out please?
You should except KeyError for invalid keys not ValueError
so just change the try/except to:
try:
return answer[code]
except KeyError:
print('invalid ICD-code')
The reason you're receiving a KeyError rather than a ValueError is that Python raises a KeyError when you attempt to get data for a key which doesn't exist in the dictionary. Therefore, no ValueError is raised and your catch block is never entered. The simple solution to this would be to change the type of exception you're trying to catch to KeyError. However, using exception handling as execution flow is generally a bad practice so I would suggest checking if the key exists in the dictionary before retrieving it:
if code in answer:
return answer[code]
else:
raise ValueError('invalid ICD-code')
As an aside, I would suggest that you avoid having an open file as a field on your class. In general, it's a good idea to open files, read them and close them as one operation. Also, your code is attempting to read the data in the file every time you try to access a code from the dictionary, so this will only work for the first code. Instead, you should try this:
class Hippocrates:
def __init__(self, location):
self.codes = {}
file = open(location, "r")
data = file.readline()
while data:
current = data.split()
x = current.pop(0)
current = ' '.join(current)
self.codes[x] = current
data = file.readline()
file.close()
def description(self, code):
if code in self.codes:
return self.codes[code]
else:
raise ValueError('invalid ICD-code')
if you use dict[key]
then it will raise a KeyError if the key does not exist.
if you use dict.get(key) then it will return None if the key does not exist.
class Hippocrates:
def __init__(self, location):
self.file = open(location, "r")
def description(self, code):
answer = {}
data = self.file.readline()
while data:
current = data.split()
x = current.pop(0)
current = ' '.join(current)
answer[x] = answer.get(current, current)
data = self.file.readline()
try:
return answer[code]
except KeyError:
print('invalid ICD-code')
a = 'abc'
print(a.index('q'))
it obviously returns -1 but also an exception, so my question is simply how to remove the exception from the output and let -1 as the output only.
I would rather say handle the exception instead of trying to override the built-in function like:
try:
ind = string.index(substring)
print (ind)
except:
return f"{substring} does not exist in {string}"
But if you still want to override it here is one of the approach:
def index(string, substring):
try:
ind = string.index(substring)
return ind
except:
return f"{substring} does not exist in {string}"
a = "abc"
print(index(a, 'c'))
I want to assign variables like this:
Title = 'Lord of the Rings'
The issue here is that the value isn't always available, sometimes the server doesn't respond well and I get error that crashes my code.
And I have a lot of variables to assign.
So I decided to create a function that uses try and except:
def try_this(identificator, value):
try:
identificator = value
print(identificator,': ',value)
except:
traceback.print_exc
for item in items:
try_this(ebay_Title, item.title)
try_this(ebay_Price, item.currentPrice.value)
#etc...
Expected behavior:
ebay_title : 'Lord of the rings'
Actual behavior:
'Lord of the rings' : 'Lord of the rings'
Any errors in your code will occur at the point when you try to read item.title or item.currentPrice.value. So your proposed solution will still crash just before calling try_this.
When you call a function, Python first computes the values of the arguments, then assigns those values to variables in the function. So Python will try to read item.title or item.currentPrice.value before it calls try_this, then it will load try_this and assign the value it got to the local value variable inside the function. After that, there is no possibility of an error: you can always assign value to another variable.
So you need to wrap the reference to item.title or item.currentPrice.value in a try/except block.
This is the most straightforward way to do that:
try:
ebay_Title = item.title
except:
# handle error here, e.g., assign a default value
traceback.print_exc()
try:
ebay_Price = item.currentPrice.value
except:
# handle error here, e.g., assign a default value
traceback.print_exc()
As you've seen, this can get tedious. In your case, the error always occurs when you try to read an attribute, so you could write code specifically to handle that:
def get_attr(obj, attr_name, default_value=None):
try:
return getattr(obj, attr_name)
except:
# handle any errors here
traceback.print_exc()
return default_value
ebay_Title = get_attr(item, 'title')
# handle missing value (optional)
if ebay_Title is None:
...
ebay_Price = get_attr(get_attr(item, 'currentPrice'), 'value')
# handle missing value (optional)
if ebay_Price is None:
...
Note that get_attr is very similar to the built-in getattr function, but it will catch any kind of error that occurs when trying to read the attribute, not just missing-attribute errors.
If you don't like the get_attr(get_attr(item, 'currentPrice'), 'value') syntax, you could have get_attr accept a dot-separated attribute list like this:
def get_attr(obj, attr_name, default_value=None):
try:
val = obj
for attr in attr_name.split('.'):
val = getattr(val, attr)
return val
except:
# handle any errors here
traceback.print_exc()
return default_value
ebay_Title = get_attr(item, 'title')
ebay_Price = get_attr(item, 'currentPrice.value')
As said before, you really don't want to do this but here is how you can.
def try_this(identificator, value):
try:
globals()[identificator] = value
print(identificator,': ',value)
except:
traceback.print_exc
for item in items:
try_this("ebay_Title", item.title)
try_this("ebay_Price", item.currentPrice.value)
It would be better to use a predeclared object like:
itemDict = {
"ebay_Title": "",
"ebay_Price": ""
}
def try_this(identificator, value):
try:
itemDict[identificator] = value
print(identificator,': ',value)
except:
traceback.print_exc
for item in items:
try_this("ebay_Title", item.title)
try_this("ebay_Price", item.currentPrice.value)
In the comments thread, the OP stated:
It's a server side error, the actual function I use is a bit more complex: for item in response.reply.searchResult.item: And sometimes the server says there's not searchResult in response.reply The the code crushes – V-cash 28 mins ago
To deal with searchResult not existing, I would do this in that portion:
for item in getattr(response.reply, 'searchResult', []):
...
If searchResult does not exist in response.reply, it will return an empty list, preventing an error and making the loop do nothing.
I am getting an exception deep in some loop in a function in a long running process. If I get an exception, I would like to log a thing that is at the index in the loop at which the exception occurred. Unfortunately, the information that I need isn't available in the current function... it's in the next function up the stack. However, the index isn't available in the next function up the stack, it is only available in the current function. In order to log the appropriate info, I therefore need information from two function calls at different nesting levels. How do I pass information between functions in an Exception?
For example:
def foo():
information_I_need = ["some", "arbitrary", "things"]
data_operated_on = list(range(0, 10*len(information_I_need), 10)) #0,10,20
#NB: these two lists are the same size
try:
bar(data_operated_on)
except ValueError as e:
i = e.get_the_index_where_bar_failed()
print(information_I_need[i])
def bar(aoi):
for i in range(len(aoi)):
try:
fails_on_10(aoi[i])
except ValueError as e:
e.add_the_index_where_bar_failed(i)
raise e
def fails_on_10(n):
if n == 10:
raise ValueError("10 is the worst!")
The expected behavior here would be that a call to foo() prints "arbitrary".
In this example, bar has information (namely, the index i) that foo needs to correctly report the problem. How do I get that information from bar up to foo?
You can add the index as an attribute of the exception object.
It's best to do this with a custom exception class, rather than using one of the built-in exceptions.
class BadInformation(Exception):
def __init__(self, message, index):
# py2/3 compat
# if only targeting py3 you can just use super().__init__(message)
super(BadInformation, self).__init__(message)
self.bad_index = index
def foo():
information_I_need = ["some", "arbitrary", "things"]
data_operated_on = list(range(0, 10*len(information_I_need), 10)) #0,10,20
#NB: these two lists are the same size
try:
bar(data_operated_on)
except BadInformation as e:
i = e.bad_index
print(information_I_need[i])
def bar(aoi):
# if you need both the index and value, use `enumerate()`
for index, value in enumerate(aoi):
try:
fails_on_10(value)
except ValueError as e:
raise BadInformation(str(e), index)
## on py 3 you may want this instead
## to keep the full traceback
# raise BadInformation(str(e), index) from e
def fails_on_10(n):
if n == 10:
raise ValueError("10 is the worst!")
I don't quite understand yet how to correctly use exceptions in Python. I want to process the data I can't completely trust (they are prone to change, and if they change, script may break). Let's say I process a webpage using BeautifulSoup. If author of the website make some changes to his website, some statements may rise exception. Let's look at this code example:
data = urllib2.urlopen('http://example.com/somedocument.php').read()
soup = BeautifulSoup(data, convertEntities="html")
name = soup.find('td', text=re.compile(r'^Name$')).parent.nextSibling.string
print name
Now, if soup.find() fail because owner of that website will change content of the website and rename cell Name to Names, an exception AttributeError: 'NoneType' object has no attribute 'parent' will be raised. But I don't mind! I expect that some data won't be available. I just want to proceed and use what variables I have available (of course there will be some data I NEED, and if they are unavailable I will simply exit.
Only solution I came up with is:
try: name = soup.find('td', text=re.compile(r'^Name$')).parent.nextSibling.string
except AttributeError: name = False
try: email = soup.find('td', text=re.compile(r'^Email$')).parent.nextSibling.string
except AttributeError: email = False
try: phone = soup.find('td', text=re.compile(r'^Phone$')).parent.nextSibling.string
except AttributeError: phone = False
if name: print name
if email: print email
if phone: print phone
Is there any better way, or should I just continue making try-except for every similar statement? It doesn't look very nice at all.
EDIT: Best solution for me would be like this:
try:
print 'do some stuff here that may throw and exception'
print non_existant_variable_that_throws_an_exception_here
print 'and few more things to complete'
except:
pass
This would be great, but pass will skip anything in try code block, so and few more things to complete will never be printed. If there was something like pass, but it would just ignore the error and continue executing, it would be great.
Firstly, if you don't mind the exception you can just let it pass:
try:
something()
except AttributeError:
pass
but never do this as it is will let all errors pass:
try:
something()
except Exception:
pass
As for your code sample, perhaps it could be tidied up with something like this:
myDict = {}
for item in ["Name", "Email", "Phone"]:
try:
myDict[item] = soup.find('td', text=re.compile(r'^%s$' % item)).parent.nextSibling.string
except Attribute
myDict[item] = "Not found"
for item in ["Name", "Email", "Phone"]:
print "%s: %s" % (item, myDict[item])
Have you tried using a try/finally statement instead?
http://docs.python.org/tutorial/errors.html#defining-clean-up-actions
Example from the docs:
>>> def divide(x, y):
... try:
... result = x / y
... except ZeroDivisionError:
... print "division by zero!"
... else:
... print "result is", result
... finally:
... print "executing finally clause"
So, to use your example:
try:
do_some_stuff_here_that_may_throw_an_exception()
except someError:
print "That didn't work!"
else:
print variable_that_we_know_didnt_throw_an_exception_here
finally:
print "finishing up stuff"
"finally" will always excecute, so that's where you can put your "finishing up" stuff.