error handling in user input: is this natural or just laborious

S

sam

hi all,

i'm starting to put together a program to simulate the performance of
an investment portfolio in a monte carlo manner doing x thousand
iterations and extracting data from the results.

i'm still in the early stages, and am trying to code something simple
and interactive to get the percentages of the portfolio in the five
different investment categories. i thought i'd get in with the error
handling early so if someone types in something wrong (like a word), or
the numbers don't add up to 100%, the error would be caught immediately
and the user sent back to the start of the loop. granting that there
may be better ways of doing this, if i decide that i do want to do it
like this (i.e. a single error requires all data to be re-entered, not
unreasonable for only five items), is this a good way of doing it or a
confusing way of doing it from the perspective of readability and
maintenance:

while True:

cash, bond, blue, tech, dev = 0,0,0,0,0
check=False

try:
cash=input('Please enter a cash percentage for the portfolio: ')
except NameError:
print 'That is not a number. Please start again and remember to enter
integers.'
else:
try:
bond=input('Please enter a bond portfolio for the portfolio: ')

except NameError:
print 'That is not a number. Please start again and remember to
enter integers.'
else:
try:
blue=input('Please enter a blue-chip percentage for the portfolio:
')
except NameError:
print 'That is not a number. Please start again and remember to
enter integers.'
else:
try:
tech=input('Please enter a tech stocks percentage for the
portfolio: ')
except NameError:
print 'That is not a number. Please start again and remember to
enter integers.'
else:
try:
dev=input('Please enter a developing countries index for the
portfolio: ')
check=True
except NameError:
print 'That is not a number. Please start again and remember to
enter integers.'

if cash+bond+blue+tech+dev==100:
break
if cash+bond+blue+tech+dev!=100 and check!= False:
print 'Those numbers do not sum to 100. Please start again.'



i know it's a bit verbose, but it was the nested try clauses i was
really wondering about. is the code immediate legible to experienced
python users? or does it look like gibberish at first?

just hoped for some fresh eyes on it.

thanks,

sam

PS making check=True just saves the code from printing 'those numbers
don't sum to 100' if they haven't all been entered, which looks kind of
silly.
 
J

James Stroud

sam said:
hi all,

i'm starting to put together a program to simulate the performance of
an investment portfolio in a monte carlo manner doing x thousand
iterations and extracting data from the results.

i'm still in the early stages, and am trying to code something simple
and interactive to get the percentages of the portfolio in the five
different investment categories. i thought i'd get in with the error
handling early so if someone types in something wrong (like a word), or
the numbers don't add up to 100%, the error would be caught immediately
and the user sent back to the start of the loop. granting that there
may be better ways of doing this, if i decide that i do want to do it
like this (i.e. a single error requires all data to be re-entered, not
unreasonable for only five items), is this a good way of doing it or a
confusing way of doing it from the perspective of readability and
maintenance:

while True:

cash, bond, blue, tech, dev = 0,0,0,0,0
check=False

try:
cash=input('Please enter a cash percentage for the portfolio: ')
except NameError:
print 'That is not a number. Please start again and remember to enter
integers.'
else:
try:
bond=input('Please enter a bond portfolio for the portfolio: ')

except NameError:
print 'That is not a number. Please start again and remember to
enter integers.'
else:
try:
blue=input('Please enter a blue-chip percentage for the portfolio:
')
except NameError:
print 'That is not a number. Please start again and remember to
enter integers.'
else:
try:
tech=input('Please enter a tech stocks percentage for the
portfolio: ')
except NameError:
print 'That is not a number. Please start again and remember to
enter integers.'
else:
try:
dev=input('Please enter a developing countries index for the
portfolio: ')
check=True
except NameError:
print 'That is not a number. Please start again and remember to
enter integers.'

if cash+bond+blue+tech+dev==100:
break
if cash+bond+blue+tech+dev!=100 and check!= False:
print 'Those numbers do not sum to 100. Please start again.'



i know it's a bit verbose, but it was the nested try clauses i was
really wondering about. is the code immediate legible to experienced
python users? or does it look like gibberish at first?

just hoped for some fresh eyes on it.

thanks,

sam

PS making check=True just saves the code from printing 'those numbers
don't sum to 100' if they haven't all been entered, which looks kind of
silly.

It looks pretty rough. I think tabs are a very bad idea. Patently. Tabs
should be reserved for tables, for which tabs were named. If they were
meant to be used for indenting, they would have been named "indenters".

Try blocks in my opinion are a good idea but they need to be done
correctly. You should also consider thinking more in terms of procedural
programming than linear.

Also, the best way would be by gui. See this book for the best resource
(IMHO) for python gui programming:

http://www.manning.com/grayson/

I think the following is much better technique and is more fun than
laborious to code:


def get_pcts(prompts):
prompts = ['gross product', 'repleat divisional', 'sales quota']
pcts = [get_pct(prompt) for prompt in prompts]
sum_pcts = sum(pcts)
if (sum_pcts > 100.0001) or (sum_pcts < 99.999):
raise ValueError, "Values didn't add to 100."
return pcts

def get_pct(pct_type):
pct = raw_input('Input percent of %s:' % pct_type)
return float(pct)

def get_values():
prompts = ['gross product', 'net outsource', 'sales quota']
while True:
try:
pcts = get_pcts(prompts)
return dict(zip(prompts, pcts))
except ValueError, e:
print e
print 'Try again dude.'

"""
Here's a testInput percent of gross product:21
Input percent of repleat divisional:22
Input percent of sales quota:57
{'sales quota': 57.0, 'gross product': 21.0, 'net outsource': 22.0}
"""

James

--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com/
 
J

James Stroud

James said:
sam said:
hi all,

i'm starting to put together a program to simulate the performance of
an investment portfolio in a monte carlo manner doing x thousand
iterations and extracting data from the results.

i'm still in the early stages, and am trying to code something simple
and interactive to get the percentages of the portfolio in the five
different investment categories. i thought i'd get in with the error
handling early so if someone types in something wrong (like a word), or
the numbers don't add up to 100%, the error would be caught immediately
and the user sent back to the start of the loop. granting that there
may be better ways of doing this, if i decide that i do want to do it
like this (i.e. a single error requires all data to be re-entered, not
unreasonable for only five items), is this a good way of doing it or a
confusing way of doing it from the perspective of readability and
maintenance:

while True:

cash, bond, blue, tech, dev = 0,0,0,0,0
check=False

try:
cash=input('Please enter a cash percentage for the portfolio: ')
except NameError:
print 'That is not a number. Please start again and remember
to enter
integers.'
else:
try:
bond=input('Please enter a bond portfolio for the
portfolio: ')

except NameError:
print 'That is not a number. Please start again and
remember to
enter integers.'
else:
try:
blue=input('Please enter a blue-chip percentage for
the portfolio:
')
except NameError:
print 'That is not a number. Please start again and
remember to
enter integers.'
else:
try:
tech=input('Please enter a tech stocks percentage
for the
portfolio: ')
except NameError:
print 'That is not a number. Please start again
and remember to
enter integers.'
else:
try:
dev=input('Please enter a developing countries
index for the
portfolio: ')
check=True
except NameError:
print 'That is not a number. Please start
again and remember to
enter integers.'

if cash+bond+blue+tech+dev==100:
break
if cash+bond+blue+tech+dev!=100 and check!= False:
print 'Those numbers do not sum to 100. Please start again.'



i know it's a bit verbose, but it was the nested try clauses i was
really wondering about. is the code immediate legible to experienced
python users? or does it look like gibberish at first?

just hoped for some fresh eyes on it.

thanks,

sam

PS making check=True just saves the code from printing 'those numbers
don't sum to 100' if they haven't all been entered, which looks kind of
silly.

It looks pretty rough. I think tabs are a very bad idea. Patently. Tabs
should be reserved for tables, for which tabs were named. If they were
meant to be used for indenting, they would have been named "indenters".

Try blocks in my opinion are a good idea but they need to be done
correctly. You should also consider thinking more in terms of procedural
programming than linear.

Also, the best way would be by gui. See this book for the best resource
(IMHO) for python gui programming:

http://www.manning.com/grayson/

I think the following is much better technique and is more fun than
laborious to code:


def get_pcts(prompts):
prompts = ['gross product', 'repleat divisional', 'sales quota']
pcts = [get_pct(prompt) for prompt in prompts]
sum_pcts = sum(pcts)
if (sum_pcts > 100.0001) or (sum_pcts < 99.999):
raise ValueError, "Values didn't add to 100."
return pcts

def get_pct(pct_type):
pct = raw_input('Input percent of %s:' % pct_type)
return float(pct)

def get_values():
prompts = ['gross product', 'net outsource', 'sales quota']
while True:
try:
pcts = get_pcts(prompts)
return dict(zip(prompts, pcts))
except ValueError, e:
print e
print 'Try again dude.'

"""
Here's a testInput percent of gross product:21
Input percent of repleat divisional:22
Input percent of sales quota:57
{'sales quota': 57.0, 'gross product': 21.0, 'net outsource': 22.0}
"""

James

Oops, I forgot to remove a line in the final code:

def get_pcts(prompts):
pcts = [get_pct(prompt) for prompt in prompts]
sum_pcts = sum(pcts)
if (sum_pcts > 100.0001) or (sum_pcts < 99.999):
raise ValueError, "Values didn't add to 100."
return pcts

def get_pct(pct_type):
pct = raw_input('Input percent of %s:' % pct_type)
return float(pct)

def get_values():
prompts = ['gross product', 'net outsource', 'sales quota']
while True:
try:
pcts = get_pcts(prompts)
return dict(zip(prompts, pcts))
except ValueError, e:
print e
print 'Try again dude.'


"""
Testing:
Input percent of gross product:22
Input percent of net outsource:21
Input percent of sales quota:57
{'sales quota': 57.0, 'gross product': 22.0, 'net outsource': 21.0}Input percent of gross product:15.1
Input percent of net outsource:88.2
Input percent of sales quota:19.8
Values didn't add to 100.
Try again dude.
Input percent of gross product:bob
invalid literal for float(): bob
Try again dude.
"""

--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com/
 
S

sam

you're right, of course. it occurred to me that, even if it were
manageable for a few items, it would quickly become absurd as the
number of items grew. this:

def get_pct():
while True:
pct_list=[['cash', 0], ['bond', 0], ['blue', 0], ['tech', 0], ['dev',
0]]
total=0
for i in range(len(pct_list)):
pct_list[1]=input('Please enter the percentage value for %s: '
%pct_list[0])
total+=pct_list[1]
if total == 100:
return pct_list
break
else:
print "You've messed up, do it again..."

is much better, and easily maintainable by adding to the assignment for
pct_list. still leaves me with checking for strings though, in case
someone types 'donut' at the prompt...
 
S

sam

this does what i want, though i don't like the inner while loop having
to be there


def get_pct():
while True:
pct_list=[['cash', 0], ['bond', 0], ['blue', 0], ['tech', 0], ['dev',
0]]
total=0
for i in range(len(pct_list)):
while True:
pct_list[1]=raw_input('Please enter the percentage value for %s:
' %pct_list[0])
if pct_list[1].isdigit() == False:
print "You've messed up, do it again..."
else:
total+=int(pct_list[1])
break
if total == 100:
return pct_list
break
else:
print "You've messed up, do it again..."


gosh, a lot of work to get some input. i must be missing something,
though this is a lot better than what i had before...
 
J

James Stroud

sam said:
this does what i want, though i don't like the inner while loop having
to be there


def get_pct():
while True:
pct_list=[['cash', 0], ['bond', 0], ['blue', 0], ['tech', 0], ['dev',
0]]
total=0
for i in range(len(pct_list)):
while True:
pct_list[1]=raw_input('Please enter the percentage value for %s:
' %pct_list[0])
if pct_list[1].isdigit() == False:
print "You've messed up, do it again..."
else:
total+=int(pct_list[1])
break
if total == 100:
return pct_list
break
else:
print "You've messed up, do it again..."


gosh, a lot of work to get some input. i must be missing something,
though this is a lot better than what i had before...


I think youwill like your code better if you take nested loops and put
them in functions

In this following block, I have made changes to your revised code to
show better technique on the small scale (removing tabs so it would
format correctly without wrapping and breaking lines):

def get_pct():
while True:

# for things like your pct_list, begin with dictionaries in mind
# the pairs you had were unnecessary clutter
pct_list = ['cash', 'bond', 'blue', 'tech', 'dev']


# clean up code by moving long message strings outside of loops
# this will help you see the logic of your code later
msg = 'Please enter the percentage value for %s: '

# Hmm--running totals, big PITA
total=0

# here is the dictionary we will be constructing
pct_dict = {}

# loop over lists and not over list indices
# usually watch when you want to write "range(len(some_list))"
for a_pct in pct_list:

# 3rd level nested loop: think function
while True:

# see the python style guide for where to put spaces
# around such things as equals signs
pct_dict[a_pct] = raw_input(msg % a_pct)

# doesn't seem to hit at the heart of what you want, but
# I'll keep it for pedagogy (I would try to coerce to float
# and catch
# also, use not instead of "== False"
# e.g: if not pct_list[a_pct].isdigit()
# better yet, put the positive case in the "if" statement
try:

# maybe even better would be to total later so you
# don't have to keep track of a running total
# also, coercing to int here anyway, so just make it
# the test with try:except
total += int(pct_list[a_pct])
break

except ValueError, e:
print "You've messed up, do it again..."

# the pct_list may add to 100 as floats, but int rounding
# might kill the total and make the test fail
# perhaps add as floats and test for a small range around
# 100 that allows for float arithmetic error
if total == 100:
return pct_list
else:
print "You've messed up, do it again..."


That's as few changes I could make to it. Let's see it without the comments:


def get_pct():
while True:
pct_list = ['cash', 'bond', 'blue', 'tech', 'dev']
msg = 'Please enter the percentage value for %s: '
total=0
pct_dict = {}
for a_pct in pct_list:
while True:
pct_dict[a_pct] = raw_input(msg % a_pct)
try:
total += int(pct_list[a_pct])
break
except ValueError, e:
print "You've messed up, do it again..."
if total == 100:
return pct_dict
else:
print "You've messed up, do it again..."


A little cleaner. Now, lets tighten it up a bit more, and put nested
loops into functions. Im getting rid of keeping track of the running
total, that will clean a lot. I'm also going to be a little more
mathematically saavy.

def get_pct():
while True:
pct_list = ['cash', 'bond', 'blue', 'tech', 'dev']
pct_dict = get_pct_dict(pct_list)
total = sum(pct_dict.values())
if (total < 99.999) or (total > 100.001):
print "You've messed up, do it again"
else:
return pct_dict()

def get_pct_dict(pct_list):
pct_dict = {}
for a_pct in pct_list:
pct_dict[a_pct] = get_one_pct_value(a_pct)
return pct_dict()

def get_one_pct_value(a_pct):
msg = 'Please enter the percentage value for %s: '
while True:
try:
return float(msg % a_pct)
except ValueError:
print "You messed up, try again."

Now, all testing is done at the point where it is needed. There are no
running totals that could cause accounting errors, your final data
structure is an easy to use dict, unecessary tests have been eliminated,
loops have been de-nested visually and logically, and, most importantly,
the indentation level is kept manageable.

I think you will be able to see how this latter code evolved from yours.
I used to program just like you and it has taken me a few years to
develop these little rules for tightening my code.

James

--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com/
 
J

James Stroud

James said:
sam said:
this does what i want, though i don't like the inner while loop having
to be there
[snip]
A little cleaner. Now, lets tighten it up a bit more, and put nested
loops into functions. Im getting rid of keeping track of the running
total, that will clean a lot. I'm also going to be a little more
mathematically saavy.

def get_pct():
while True:
pct_list = ['cash', 'bond', 'blue', 'tech', 'dev']
pct_dict = get_pct_dict(pct_list)
total = sum(pct_dict.values())
if (total < 99.999) or (total > 100.001):
print "You've messed up, do it again"
else:
return pct_dict()

def get_pct_dict(pct_list):
pct_dict = {}
for a_pct in pct_list:
pct_dict[a_pct] = get_one_pct_value(a_pct)
return pct_dict()

def get_one_pct_value(a_pct):
msg = 'Please enter the percentage value for %s: '
while True:
try:
return float(msg % a_pct)
except ValueError:
print "You messed up, try again."

Now, all testing is done at the point where it is needed. There are no
running totals that could cause accounting errors, your final data
structure is an easy to use dict, unecessary tests have been eliminated,
loops have been de-nested visually and logically, and, most importantly,
the indentation level is kept manageable.

I think you will be able to see how this latter code evolved from yours.
I used to program just like you and it has taken me a few years to
develop these little rules for tightening my code.

James

Of course I see room for improvement in my own code.

Personally, I like to remove "values" from logic because it results,
naturally, in more versatile functions. So I would redo my get_pct()
method to take the list as a parameter:


def get_pct(pct_list):
while True:
pct_dict = get_pct_dict(pct_list)
total = sum(pct_dict.values())
if (total < 99.999) or (total > 100.001):
print "You've messed up, do it again"
else:
return pct_dict()


Now, its a much more versatile piece of code, e.g.:


pct_list = ['cash', 'bond', 'blue', 'tech', 'dev']
pct_dict = get_pct(pct_list)


And now all of your code boils down to these latter 2 statments.

You might want to think about what you would do to pass the total
criterion is as a parameter rather than hard coding it. Hint:

def get_pct(pct_list, criterion, slack):
[you fill in here]
if (total < (criterion - slack)) or {total > (criterion + slack)):
[you fill in more here]

Now, no values are hard-coded but are passed in as parameters. Perhaps
"msg" in get_one_pct_value() could be treated the same way to produce
yet more flexibility and hence re-usability.

James

--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com/
 
S

Steven D'Aprano

gosh, a lot of work to get some input. i must be missing something,
though this is a lot better than what i had before...

Welcome to the real world of programming. Writing your algorithms is,
generally, the easy part. Handling data input and output and the user
interface is often much more work.

I would suggest you factor out logically separate parts of your code
something like this:

done = False
good_input = False
while not done:
while not good_input:
a, b, c, d = get_user_input() # or whatever values you need
good_input = validate_user_input(a, b, c, d)
done = do_something_with_input(a, b, c, d)


See how easy it is to understand the high level logic of the code,
without getting bogged down in gory details? Now all you have to do is
write functions get_user_input() etc.

E.g. I might write something like this:

class RangeError(ValueError):
pass

def validate_user_input(a, b, c, d):
# assumes all of a, b, c, d are percentages
data = {'a': a, 'b':b, 'c': c, 'd': d}
for name, value in data:
try:
float(value)
if not (0.0 <= value <= 100.0):
raise RangeError
except RangeError: # this must come first
print name, "is out of range."
return False
except ValueError:
print name, "is not a percentage."
return False
return True
 
B

bruno de chez modulix en face

sam a écrit :

(snip)
i'm still in the early stages, and am trying to code something simple
and interactive to get the percentages of the portfolio in the five
different investment categories. i thought i'd get in with the error
handling early so if someone types in something wrong (like a word), or
the numbers don't add up to 100%, the error would be caught immediately
and the user sent back to the start of the loop.
(snip)

You may want to have a look at the formencode package. While mostly
used in web apps, it's perfectly usable for all kind of
python<->outside world data conversion/validation.

HTH
 
S

sam

a huge amount to think about there. special thanks to james for taking
the time to make such detailed responses. the problem is that even
though nested loops and the like place a heavy analytical burden on the
programmer (i.e. me) as he tries to remember what does what,
conceptualizing a program as a collection of classes and functions tied
together with a minimal amount of code brings to bear what is, at the
moment, an even greater burden.

my gut feeling is that, over time (as james said was true in his own
case), and with practice, the relative weights of these two burdens
change so that the procedural approach becomes easier to adopt, and its
benefits of readability and maintainability easier to enjoy. fingers
crossed...

i will continue coding the monte carlo machine until it does what i
want in more or less clumsy fashion, and might put it up here if it
feels like it would be of interest.

thanks again,

sam
 
L

Lawrence D'Oliveiro

sam said:
i'm still in the early stages, and am trying to code something simple
and interactive to get the percentages of the portfolio in the five
different investment categories. i thought i'd get in with the error
handling early so if someone types in something wrong (like a word), or
the numbers don't add up to 100%, the error would be caught immediately
and the user sent back to the start of the loop. granting that there
may be better ways of doing this, if i decide that i do want to do it
like this (i.e. a single error requires all data to be re-entered, not
unreasonable for only five items) ...

One obvious thing is to remove repetitiveness from the code by collecting
the different cases in a data table. E.g.

data = {}
input_control = \
[
{
"key" : "cash",
"description" : "cash percentage",
"type" : int,
"describe_type" : "number",
},
{
"key" : "bond",
"description" : "bond portfolio",
"type" : int,
"describe_type" : "number",
},
{
"key" : "blue",
"description" : "blue-chip percentage",
"type" : int,
"describe_type" : "number",
},
{
"key" : "tech",
"description" : "tech stocks percentage",
"type" : int,
"describe_type" : "number",
},
{
"key" : "dev",
"description" : "developing countries percentage",
"type" : int,
"describe_type" : "number",
},
]

while True :
index = 0
while index != len(input_control) :
control = input_control[index]
try :
data[control["key"]] = \
control["type"](raw_input(
"Please enter a %s index for the portfolio: "
%
control["description"]
))
index += 1
except ValueError :
print "That is not a %s." % control["describe_type"]
#end try
#end while
if sum([data[k] for k in data]) == 100 :
break
print "Those numbers do not sum to 100. Please start again."
#end while
 
S

Steve Holden

Lawrence said:
In message <[email protected]>, James Stroud
wrote:
Or possibly "inds", in a similarly abbreviative spirit.
Really? I thought they were for tabulators
<http://www.columbia.edu/acis/history/tabulator.html>.

Well, let's ignore the fact that this was probably a tongue-in-cheek reply.

Firstly, the article you quote shows clearly that Hollerith invented the
tabulator in the late 19th century. C L Scholes was granted the first
typewriter paten in 1868, and Remington started manufacture in 1873. So
tabs were already used (to produce tables) by the time the tabulator
arrived on the scene.

Secondly the tabulator was originally simply a device for counting the
occurrences of the various values possible for a field (you may know it
was invented to assist in the processing of the US census), and when
printing was later added the output to the printer wasn't sequential
anyway, so no "tab character" was required: the positions of report
fields would be hard-programmed into the patch panel.

ObPython: PEP 8 (http://www.python.org/dev/peps/pep-0008/) was
originally an essay by Guido in which he listed his own personal
stylistic preferences. Naturally his opinions were taken seriously
because he has proved to be a capable language designer, and when the
PEP scheme was introduced his essay (originally published under the
title "A Foolish Consistency is the Hobgoblin of Little Minds") was
adopted more or less unchanged. It has since been revised, but still
refers to the original. Ultimately a conformance with PEP 8 can be taken
as an indication that the author has reached a certain stylistic maturity.

regards
Steve
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top