Calling a function recursively for user input

2019-08-15 00:23发布

问题:

I'm trying to make a rock-paper-scissors game, and am trying to verify the input.

def player1():
    x = (raw_input("please select: Rock(r)/Paper(p)/Scissors(s): ")).lower()
    if x == 'r' or x == 'p' or x == 's' or x == 'rock' or x == 'paper' or x == 'scissors':
        return x[0]
    else:
        print "Error - wrong input!"
        player1() #I know I can run a While loop, but I need to run it this way.

print(player1())

If I enter the right input on the first try, everything works fine. But if I enter wrong input on first try, and enter the right input on the second time, I get None in the output, instead of the first letter of the RPS options.

What am I missing?

回答1:

You're going to want to loop on input. What you're doing currently is recursively calling player1, and the recursive case doesn't have an explicit return value (hence, None is returned).

The way you do this is simple: while there is invalid input, prompt again. I'm using a modified version of this in the vein of the "while True break" style; it accomplishes the same goal. We loop indefinitely, and if the condition we want is valid, we return; otherwise, we prompt for input and loop again.

def player1():
    while True:
        x = raw_input("please select: Rock(r)/Paper(p)/Scissors(s): ").lower()
        if x == 'r' or x == 'p' or x == 's' or x == 'rock' or x == 'paper' or x == 'scissors':
            return x[0]
        else:
            print "Error - wrong input!"

As an alternative to that if statement, there is a slightly more clean way to express it via the in operator.

if x in ('r', 'p', 's', 'rock', 'paper', 'scissors'):

As an addendum to your original question (as it says that you have to do it recursively), I must strongly caution you against doing any input evaluation through recursion. Python has a call stack size of around 1,000, which means that you have a very finite (yet reasonably large) amount of tries before the program irrecoverably crashes.

Not just that, but your operation stack will be unnecessarily filled with method calls that behave in a similar fashion to a loop. For memory's sake in addition to the absolute recursion ceiling, do not use recursion for this.

If you absolutely must, and again I strongly advise against doing this, then you simply have to return from your iterative case.

def player1():
    x = (raw_input("please select: Rock(r)/Paper(p)/Scissors(s): ")).lower()
    if x == 'r' or x == 'p' or x == 's' or x == 'rock' or x == 'paper' or x == 'scissors':
        return x[0]
    else:
        print "Error - wrong input!"
        return player1() #I know I can run a While loop, but I need to run it this way.

print(player1())


回答2:

An improved version of @Makoto's example:

def player1():
    x = raw_input("please select: Rock(r)/Paper(p)/Scissors(s): ").lower()
    while True:
        if x in ['r', 'p', 's', 'rock', 'paper', 'scissors']:
            return x[0]
        else:
            print "Error - wrong input!"
            x = raw_input("please select: Rock(r)/Paper(p)/Scissors(s): ").lower()

This is a little more concise than many or expressions which gets a bit unwieldy if you have a lot of conditions you want to check!

A little explanation:

We're checking to see x (our user input) is in pre-defined list of valid inputs.

An even more generalized version of this which becomes reusable:

Example: (reusable, non-recursion:)

#!/usr/bin/env python


from __future__ import print_function  # For Python 2/3 compat


try:
    input = raw_input  # For Python 2/3 compat
except NameError:
    pass


def prompt(prompt="Enter: ", valid=None):
    s = input(prompt)
    while valid and s not in valid:
        print("Invalid input! Please try again. Valid inputs are {0:s}".format(" ".join(valid)))
        s = input(prompt)
    return s


x = prompt("Enter action ([r]ock, [p]aper, [s]cissors): ", ["r", "p", "s", "rock", "paper", "scissors"])

Demo:

$ python foo.py
Enter action ([r]ock, [p]aper, [s]cissors): a
Invalid input! Please try again. Valid inputs are r p s rock paper scissors
Enter action ([r]ock, [p]aper, [s]cissors): foo
Invalid input! Please try again. Valid inputs are r p s rock paper scissors
Enter action ([r]ock, [p]aper, [s]cissors): r

$ python foo.py
Enter action ([r]ock, [p]aper, [s]cissors): a
Invalid input! Please try again. Valid inputs are r p s rock paper scissors
Enter action ([r]ock, [p]aper, [s]cissors): foo
Invalid input! Please try again. Valid inputs are r p s rock paper scissors
Enter action ([r]ock, [p]aper, [s]cissors): rock

PS: Sorry I didn't answer your question using recursion. IHMO this is not a good use-case for recursion. Oh well :) However; this is pretty easy to change:

Example: (reusable, recursive)

def userprompt(prompt="Enter: ", valid=None):
    s = input(prompt)
    while valid and s not in valid:
        print("Invalid input! Please try again. Valid inputs are {0:s}".format(" ".join(valid)))
        s = userprompt(prompt, valid)
    return s


回答3:

In the else branch your function (the first call to your function, in particular) doesn’t return anything. In Python functions that do not return anything always return None implicitly.



回答4:

I'm adding this answer for completeness sake, as you do ask for a recursive solution. Here is my solution that is closest to yours:

def player1():
    x = (raw_input("please select: Rock(r)/Paper(p)/Scissors(s): ")).lower()
    if x == 'r' or x == 'p' or x == 's' or x == 'rock' or x == 'paper' or x == 'scissors':
        return x[0]
    else:
        print "Error - wrong input!"
        return player1()

As you can see, all you've forgotten is a return statement. A more readable way might be:

def player1():
    playerInput=None
    while playerInput not in ('r', 'p', 's', 'rock', 'paper', 'scissors'):
        if playerInput is not None:
            print("Error - wrong input!")
        playerInput = raw_input("please select: Rock(r)/Paper(p)/Scissors(s)").lower()
    return playerInput[0]

It would be even cleaner with a do while loop, but python lacks that construct.