0

Just a beginner question about good practice.

Take this bit of code for example. I want a particular user input, and if they enter something crazy, I obviously want to run the code inside the function again.

def get_difficulty():
    chosen_difficulty = str(raw_input("***  Choose difficulty: Easy, Medium or Hard: ").lower())
    if chosen_difficulty == "hard":
        return 10
    elif chosen_difficulty == "medium":
        return 15
    elif chosen_difficulty == "easy":
        return 20       
    else:
        print "***  Did you enter 'Easy', 'Medium' or 'Hard'?"
        print "***  Sorry that was passive aggressive. You obviously didn't..."
        return get_difficulty() 

Is it ok to make the function handle the else case like this? It seems inelegant; if the person entered something wrong five times the function would be nested 5 times and eventually the right answer would have to cascade down through the return of each function.

It works fine, but is there a better way?

  • 4
    What you've discovered, (calling a function from itself) is a fundamental concept in programming called [recursion](https://en.wikipedia.org/wiki/Recursion). – jedwards Apr 06 '15 at 15:13
  • 3
    . . . except that in this case it's being used to emulate a loop, and this isn't a tail-recursive language, so . . . – geometrian Apr 06 '15 at 15:14
  • @imallett I admit, I just read the title and skimmed the code. I agree a loop would be a better approach for his specific case. – jedwards Apr 06 '15 at 15:16
  • Also, you should probably add the line `chosen_difficulty = chosen_difficulty.lower()` The options you gave the user was "Easy, Medium, etc" but the options in your `if...elif` blocks user the lower case. – vitiral Apr 06 '15 at 15:50
  • It is being .lower() ed but it's just off screen :) – Pez Picacious Apr 07 '15 at 07:27

2 Answers2

3

In your case a loop is the correct way to handle repeated input:

def get_difficulty():
    while True:
        chosen_difficulty = raw_input("***  Choose difficulty: Easy, Medium or Hard: ").lower()
        try:
            return {"hard": 10, "medium": 15, "easy": 20}[chosen_difficulty]
        except KeyError:
            print "***  Did you enter 'Easy', 'Medium' or 'Hard'?"
            print "***  Sorry that was passive aggressive. You obviously didn't..."
Daniel
  • 42,087
  • 4
  • 55
  • 81
2

What you discovered is called "recursion" and there is fundamentally nothing wrong about it. It is a strong concept that often leads to elegant formulation of problems. The Fibonacci numbers are an often presented problem that is easily solvable with recursion: We want to generate the number sequence 1, 1, 2, 3, 5, 8, 13, .. so the n+1 entry is (n-1) + (n). This leads to the following algorithm:

def fibonacci(n):
    """Generate the n-th fibonacci number"""
    if n == 0 or n == 1:
        return(1)
    else:
        return(fibonacci(n-2) + fibonacci(n-1))

Every recursive function can be turned into an iterative one. For the n-th fibonacci sequence there exists a closed form, you can look it up in the wikipedia article. As you see there are two "irrational numbers" that can be expressed by sum/division of square root of two. So sometimes problems are "recursive by nature" and recursive solutions can be short, where as a iterative solution might "look ugly"/"be longer".

So recursion is in general a good thing, but in python it is not always a good solution. As you already pointed out, if a user inserts five time a wrong input the function stack will have five function calls. Python has a max recurion depth and if a user would enter consecutively wrong input he could crash the program. In case of user input this is not realy a problem, but in other cases you can run more easily into the maximal recursion depth.

Tail-recursion (which is not implemented in python) is a way to allow for arbitrary recursion depths. Haskell and lisp uses this concept, you can read more on wikipedia or this stackoverflow post.

The canonical way to handle input verification in python is a while loop as pointed out by Daniel.

Community
  • 1
  • 1
syntonym
  • 7,134
  • 2
  • 32
  • 45