My specific concerns are
- When parsing a parameter, is it intuitive for a future maintainer to understand code that depends on throwing an error?
- Is it expensive to be throwing exceptions as a matter of course for the default case? (seems like it might be according to https://stackoverflow.com/a/9859202/776940 )
Context
I have a parameter counter
that determines the name of a counter to increment, and optionally can increment by a positive or negative integer separated from the counter name by an =
. If no increment value is provided, the default magnitude of the increment is 1. The function is fed by breaking up a comma delimited list of counters and increments, so a valid input to the whole process can look like:
"counter1,counter2=2,counter3=-1"
which would increment "counter1" by 1, increment "counter2" by 2 and decrement "counter3" by 1.
How I Originally Wrote It
counterDescriptor = counterValue.split('=')
if len(counterDescriptor) == 1:
counterName = counterDescriptor[0]
counterIncr = 1
elif len(counterDescriptor) == 2:
counterName = counterDescriptor[0]
counterIncr = int(counterDescriptor[1])
else:
counterName, counterIncr = ('counterParsingError', 1)
which strikes me, as I recently came back to look at it, as overly verbose and clunky.
Is this a more or less Pythonic way to code that behavior?
def cparse(counter):
try:
desc,mag = counter.split('=')
except ValueError:
desc = counter
mag = ''
finally:
if mag == '':
mag = 1
return desc, int(mag)
With these test cases, I see:
>>> cparse("byfour=4")
('byfour', 4)
>>> cparse("minusone=-1")
('minusone', -1)
>>> cparse("equalAndNoIncr=")
('equalAndNoIncr', 1)
>>> cparse("noEqual")
('noEqual', 1)
These test cases that would have been caught how I originally wrote it (above) won't get caught this way:
>>> cparse("twoEquals=2=3")
('twoEquals=2=3', 1)
>>> cparse("missingComma=5missingComma=-5")
('missingComma=5missingComma=-5', 1)
and this last test case doesn't get caught by either way of doing it. Both make the int()
vomit:
>>> cparse("YAmissingComma=5NextCounter")
ValueError: invalid literal for int() with base 10: '5NextCounter'
I'm glad I discovered this problem by asking this question. The service that consumes this value would eventually choke on it. I suppose I could change the one line return desc, int(mag)
of the function to this:
if desc.find("=")<0 and (mag=='0' or (mag if mag.find('..') > -1 else mag.lstrip('-+').rstrip('0').rstrip('.')).isdigit()):
return desc, int(mag)
else:
return 'counterParsingError: {}'.format(desc), 1
(hat tip to https://stackoverflow.com/a/9859202/776940 for figuring out that this was the fastest way offered in that discussion to determine if a string is an integer)