What is the pythonic way to implement sequential try-excepts?
Asked Answered
R

2

6

I have to parse some numbers from file names that have no common logic. I want to use the python way of "try and thou shall be forgiven", or try-except structure. Now I have to add more than two cases. What is the correct way of doing this? I am now thinking either nested try's or try-except-pass, try-except-pass,... Which one would be better or something else? Factory method perhaps (how?)?

This has to be easily expandable in the future as there will be much more cases.

Below is what I want (does not work because only one exeption per try can exist):

try:
    # first try
    imNo = int(imBN.split('S0001')[-1].replace('.tif',''))
except:
    # second try
    imNo = int(imBN.split('S0001')[-1].replace('.tiff',''))
except:
    # final try
    imNo = int(imBN.split('_0_')[-1].replace('.tif',''))

Edit:

Wow, thanks for the answers, but no pattern matching please. My bad, put "some common logic" at the beginning (now changed to "no common logic", sorry about that). In the cases above patterns are pretty similar... let me add something completely different to make the point.

except:
    if imBN.find('first') > 0: imNo = 1
    if imBN.find('second') > 0: imNo = 2
    if imBN.find('third') > 0: imNo = 3
    ...
Recipient answered 25/4, 2012 at 14:27 Comment(10)
I don't know how pythonic it is, but check out the series of try-excepts in the lxml tutorial (lxml.de/tutorial.html). That's pretty much the only python example I've seen with many together.Involute
@ccoakley: Wow, that's really ugly code.Saturnalia
@NiklasB. Probably because it is import statements (do you know another way to achieve what they do?). Your answer certainly strikes me as pythonic for the desires of the OP.Involute
@ccoakley: Sure, there is __import__, which can be used to extract the common structure there as well.Saturnalia
@NiklasB. oooh, thanks. I actually copy/pasted the lxml example into something I wrote. I'm going to go rework some code.Involute
You can have more than one exception type per except clause.Danish
@Marcin: How does that help?!Saturnalia
@NiklasB. "does not work because only one exeption per try can exist"Danish
@ccoakley: This is what I meant: paste.pocoo.org/show/587092Saturnalia
@NiklasB. How very kind of you!Involute
S
10

You can extract the common structure and make a list of possible parameters:

tries = [
    ('S0001', '.tif'),
    ('S0001', '.tiff'),
    ('_0_', '.tif'),
]

for sep, subst in tries:
    num = imBN.split(sep)[-1].replace(subst, '')
    try:
        imNo = int(num)
        break
    except ValueError:
        pass
else:
    raise ValueError, "String doesn't match any of the possible patterns"

Update in reaction to question edit

This technique can easily be adapted to arbitrary expressions by making use of lambdas:

def custom_func(imBN):
    if 'first' in imBN: return 1
    if 'second' in imBN: return 2

tries = [
    lambda: int(imBN.split('S0001')[-1].replace('.tif','')),
    lambda: int(imBN.split('S0001')[-1].replace('.tiff','')),
    lambda: int(imBN.split('_0_')[-1].replace('.tif','')),
    lambda: custom_func(imBN),
]

for expr in tries:
    try:
        result = expr()
        break
    except:
        pass
else:
    # error
Saturnalia answered 25/4, 2012 at 14:36 Comment(5)
+1, I was just finishing up to post the same, but you beat me to it ;-)Gottuard
Instead of checking imNo is None afterwards to raise, you could also add an else clause to the loop (which executes if the loop finished without breaking). Then you don't even need the imNo = None line to start with. :)Easterling
@Dougal: Nice, I always forget about these.Saturnalia
Thanks for a great answer (to original question). Can you modify the tries list to match the edit?Recipient
@Juha: BTW, you don't need the .find() > 0 idiom in Python, we have the nicer 'substr' in s.Saturnalia
A
3

In your specific case, a regular expression will get rid of the need to do these try-except blocks. Something like this might catch your cases:

>>> import re
>>> re.match('.*(S0001|_0_)([0-9]+)\..*$', 'something_0_1234.tiff').groups()
('_0_', '1234')
>>> re.match('.*(S0001|_0_)([0-9]+)\..*$', 'somethingS00011234.tif').groups()
('S0001', '1234')
>>> re.match('.*(S0001|_0_)([0-9]+)\..*$', 'somethingS00011234.tiff').groups()
('S0001', '1234')

For your question about the serial try-except blocks, Niklas B.'s answer is obviously a great one.

Edit: What you are doing is called pattern matching, so why not use a pattern matching library? If the regex string is bothering you, there are cleaner ways to do it:

import re
matchers = []
sep = ['S0001', '_0_']
matchers.append(re.compile('^.*(' + '|'.join(sep) + ')(\d+)\..*$'))
matchers.append(some_other_regex_for_other_cases)

for matcher in matchers:
    match = matcher.match(yourstring)
    if match:
        print match.groups()[-1]

Another, more generic way which is compatible with custom functions:

import re
matchers = []
simple_sep = ['S0001', '_0_']
simple_re = re.compile('^.*(' + '|'.join(sep) + ')(\d+)\..*$')
def simple_matcher(s):
    m = simple_re.match(s)
    if m:
        return m.groups()[-1]

def other_matcher(s):
    if s[3:].isdigit():
        return s[3:]

matchers.append(simple_matcher)
matchers.append(other_matcher)

for matcher in matchers:
    match = matcher('yourstring')
    if match:
        print int(match)
Arvonio answered 25/4, 2012 at 14:55 Comment(3)
+1, I didn't know that you can do it like that also... unfortunately regular expressions are something that I or the people that might edit the code are unfamiliar with. Can you put something else than REs into matchers list?Recipient
@Recipient I added an approach which integrates bothArvonio
Yep, if the problem is only about pattern matching, this is a more specific approach and certainly cleaner than catching exceptions.Saturnalia

© 2022 - 2024 — McMap. All rights reserved.