Newbie Nested Function Problem

B

Brian Samek

I began learning python a few days ago and just wrote the following program.
I have nested three functions together. For some reason, the leave function
works fine when 'y' or 'n' are inputted, but it does not restart itself when
other things are inputted. Why is this?

Thanks,

Brian Samek

# Brian Samek
# Created 01/24/2004

# This program counts down from a user-defined integer less than or equal
# to 500.

print "This program counts down from any number less than or equal to 500"
print

# ask_number gets the number from the user and then puts it into countdown

def ask_number():
number = input("Please enter a number.\n")
if number > 500 or number - int(number) != 0 or number < 1:
print "Input positive integers less then 501 only, please."
ask_number()
else:
# countdown does the actual counting down until 0
def countdown (number):
if number != 0:
print number
number = number - 1
countdown (number)
else:
# leave asks the user if he wishes to exit
def leave():
leave = raw_input ("Type 'y' to start over - type 'n' to
exit. ")
if leave == "y":
ask_number()
elif leave == "n":
return
else:
print "Type either 'y' or 'n' please."
leave()
leave()
countdown (number)
ask_number()
 
T

Terry Carroll

I began learning python a few days ago and just wrote the following program.
I have nested three functions together. For some reason, the leave function
works fine when 'y' or 'n' are inputted, but it does not restart itself when
other things are inputted. Why is this?

I haven't tried running this, but...
# This program counts down from a user-defined integer less than or equal
# to 500.

print "This program counts down from any number less than or equal to 500"
print

# ask_number gets the number from the user and then puts it into countdown

def ask_number():
number = input("Please enter a number.\n")
if number > 500 or number - int(number) != 0 or number < 1:
print "Input positive integers less then 501 only, please."

Okay, ask_number doesn't do what you're saying. It's asking for a number,
placed into variable number, and if it gets something out of range, prints
an error message.

And then ends. It doesn't either a) repeat until it gets a valid number
or b) do anything with the number it gets, like pass it back to the caller
in a return statement, i.e.:

return number
ask_number()

Okay, you invoked ask_number, but didn't even try to get anything from it.
Normally, this would read something like:

number_in = asknumber()

Um.. it looks like you're inadvertably making this a deeply recursive
call, probably something you want to stay away from until you have regular
stuff down pat.
 
R

Rich Krauter

Looks like you are setting the variable 'leave' to the user input, and
then you are calling the function leave(), but remember that 'leave' has
been set to some string.
So say you enter 'xqz', and expect it to restart the loop when you get
to the leave call --- well, what you are doing is trying to call the
function xqz().
Rich
 
B

Brian Samek

Okay, ask_number doesn't do what you're saying. It's asking for a number,
placed into variable number, and if it gets something out of range, prints
an error message.

And then ends. It doesn't either a) repeat until it gets a valid number
or b) do anything with the number it gets, like pass it back to the caller
in a return statement, i.e.:

It does repeat if it doesn't get a valid number. The function calls itself
after printing an error mesage. For some reason the original message
formatted itself differently when I pasted it into my mail program. The
line just before the first else statement should be indented to the same
level as the line before it so it reads:

def ask_number():
number = input("Please enter a number.\n")
if number > 500 or number - int(number) != 0 or number < 1:
print "Input positive integers less then 501 only, please."
ask_number()
else:


return number


Okay, you invoked ask_number, but didn't even try to get anything from it.
Normally, this would read something like:

number_in = asknumber()

What do you mean by I "didn't even try to get anything from it." I get a
variable called "number" from it from which the countdown(number) function
counts down.
Um.. it looks like you're inadvertably making this a deeply recursive
call, probably something you want to stay away from until you have regular
stuff down pat.

I don't understand what you're saying. I designed the program as three
functions nested within each other. Are you saying I should be designing it
differently? I made it a series of nested functions because, for example, I
needed to use the "number" variable from ask_number before it was destroyed.
When a function ends, any variables it has created are destroyed.

Thanks,

Brian
 
R

Rich Krauter

I should have suggested a 'fix' before: change all the "leave"s which
are referring to, or defining the function "leave" to "leaver" or
something like that. Keep all program's references to the variable
"leave" unchanged. Then your program should work.

To clarify my previous reply, your problem is this:

leave = 'xqz'
leave()

The leave() call is actually trying to call a string object, which is
not callable.
So I wasn't quite right in what I told you in the previous post - the
leave() call is not trying to call the function xqz(), its trying to
call the string xqz.

Rich
 
B

Brian Samek

Oh wow! Thanks a lot - that was exactly the issue. I changed the variable name and the program works perfectly now. I didn't realize that a variable name in a program would have that effect.

Brian
Looks like you are setting the variable 'leave' to the user input, and then you are calling the function leave(), but remember that 'leave' has been set to some string.
So say you enter 'xqz', and expect it to restart the loop when you get to the leave call --- well, what you are doing is trying to call the function xqz().
Rich
On Sat, 2004-01-24 at 21:45, Brian Samek wrote:
 
J

Josiah Carlson

Brian said:
Oh wow! Thanks a lot - that was exactly the issue. I changed the variable name and the program works perfectly now. I didn't realize that a variable name in a program would have that effect.

Brian
Looks like you are setting the variable 'leave' to the user input, and then you are calling the function leave(), but remember that 'leave' has been set to some string.
So say you enter 'xqz', and expect it to restart the loop when you get to the leave call --- well, what you are doing is trying to call the function xqz().
Rich
One thing you should REALLY change is the way you get the number. For
general inputs, you need to deal with the fact that people may give bad
input.

try:
number = int(raw_input('prompt> '))
except KeyboardInterrupt:
#ctrl+c was pressed
return
except:
#they didn't enter a number
pass

input(<prompt>)
Will evaluate some things that are entered by the user.


Below is a non-recursive version of what you wrote, which is not limited
by the recursion limit of Python, so technically the upper bound can be
tossed. It also uses a controlled infinite loop trick that I've found
quite useful in a few projects.

- Josiah

def countdown():
while 1:
try:
number = int(raw_input("Please enter a number.\n> "))
if 1 <= number < 500:
break
except KeyboardInterrupt:
return 0
except:
pass
while number > 0:
print number
number -= 1
while 1:
leave = raw_input("Type 'y' to start over - type 'n' to exit. ")
if leave == 'y':
return 1
elif leave == 'n':
return 0
else:
print "Type either 'y' or 'n' please."

while countdown():
pass
 
T

Terry Carroll

I don't understand what you're saying. I designed the program as three
functions nested within each other. Are you saying I should be designing it
differently?

Yes. There are some sometimes good reasons to have a recursive function
(i.e., a function that calls itself), but they don't seem applicable here.
I made it a series of nested functions because, for example, I
needed to use the "number" variable from ask_number before it was destroyed.
When a function ends, any variables it has created are destroyed.

The standard way to do that is to return the variable to the caller, which
then uses it.

Here, let me give you an example of how to approach something like this.
I'm going to try to re-use as much of your logic code as possible, so we
can concentrate on the structure rather than the detail pieces. In
practice, I'd code it somewhat differently, but I don't want to go there
right now. (We can talk about that after we get the structure down, if
you like.)

You basically have four elements needed here:
1) an "ask_number" function to find out the number entered by the user;
2) a "countdown" function to do the countdown;
3) a function to ask if the user wants to repeat or leave
(One change I made here is the name; I'll discuss that in a second);
4) a main routine that puts these all together.

Here's an example of how to do the first function, ask_number:

def ask_number():
answerOK = False
while not answerOK:
number = input("Please enter a number.\n")
if number > 500 or number - int(number) != 0 or number < 1:
print "Input positive integers less then 501 only, please."
else:
answerOK = True
return number

The variable "answerOK" controls whether you have a legitimate answer or
not. It starts out false, but goes true when the user answers with a
number that meets your requirements. Once it's true, it returns that
number to the caller.

So the caller can just say:

x = ask_number()

and he's pretty much assured to get a number meeting your specification,
i.e., between 1 and 500.

Again, I'd use a different test for the if statement and a few other
things, but I wanted to preserve as much of your approach as possible.

Now, let's move on to element 2, the countdown. Your code pretty much
basically works as is:

def countdown (number):
while number != 0:
print number
number = number - 1

There are more Pythonic ways to do this, but let's leave this be.

Now, element 3, the function to ask if the user wants to repeat or leave;
I've made a trivial but important (I think) change here. You called it
"leave", but the user types 'y' if he *doesn't* want to leave, and 'n' if
he does want to leave. It makes more sense to describe (and name) the
function not in terms of whether the user wants to leave, but rather
whether he wants to continue. Also, since the function doesn't actually
do the leaving (or continuing) but just asks the question and finds out, t
makes sense to name it something that reflects that (as you did with
"as_number").

So, I've renamed the function to "ask_continue" instead of "leave".

Again, I'd code this a little differently, but leaving as much of your
code intact, you can come up with something like this:

def ask_continue():
answerOK = False
while not answerOK:
continue_answer = raw_input ("Type 'y' to start over - type 'n' to
exit. ")
if continue_answer == "y":
answerOK = True
elif continue_answer == "n":
answerOK = True
else:
print "Type either 'y' or 'n' please."
return continue_answer

Same idea here; "answerOK" is a variable that indicates whether you've
gotten a legit answer, and once you do, it returns it.

Now, to put it all together, you need that fourth element, the main
program that uses all of these. Now that you've got the blocks, the main
program is pretty straightforward:


def main():
repeat = "y"
while repeat == "y":
limit = ask_number()
countdown(limit)
repeat = ask_continue()

This is basically a big loop that repeats until the value of "repeat" is
something other than "y".

The end result here is that, when you're working on the main program, you
can really forget about how the functions work internally. All you have
to remember is what each one returns. And, as you work on each function,
you don't have to worry about the other functions at all. When you are
writing "ask_number" for example, you can make as many changes to it as
you want, and never have to fear that you'll break something in
"countdown," or in the main program. And, if you later write another
program that needs a prompt for that type of number, you can just copy
this function out and use it.

These two features, code reuse and code isolation, are the major reasons
to use functions. With the approach you originally posted, the code of
the functions are so interwoven, that you don't get either. You can't
work on that version of your program other than as a big mass. By
breaking it down, you've got a bunch of small easy-to-solve problems,
which you can approach individually.

Now, none of the above is gospel, and for each function, there are a lot
of ways to approach it, and Python has some features that would suggest
approaching them in certain ways (what we call "pythonic") that I haven't
done here (because I don't want to throw that at you right now). For
purposes of this post, don't concentrate too much on how each function
works; look at how the problem has been broken down and how hote solution
has been structured.

Hope this helps.
 

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

Staff online

Members online

Forum statistics

Threads
474,176
Messages
2,570,950
Members
47,501
Latest member
log5Sshell/alfa5

Latest Threads

Top