2

I'm trying to make do with Python's lack of a switch statement and make code more efficient by using a dictionary, but I can't quite get what I'm looking for. Here's a simplified version of the code I'm working with

def zero():
    print("Yo")


def one():
    print("Hey")

options = {
    0: zero,
    1: one,
}
while True:
    response = int(input("Number: "))
    try:
        options.get(response)()
    except TypeError:
        print("Not a valid response")

and what I would like to see is some way to break the loop such as 2: break that exits the loop. Currently I'm using sys.exit(0), but was wondering if it was possible to use the break keyword

MANA624
  • 986
  • 4
  • 10
  • 34
  • Which language are you comparing to when you say "make do with Python's lack of a switch statement"? In a C/C++ switch statement, you'd have a similar problem: `break` would just exit the switch statement, and you'd need some other way to exit the loop (see http://stackoverflow.com/questions/1420029/how-to-break-out-of-a-loop-from-inside-a-switch). – Matthias Fripp Aug 20 '16 at 20:37

3 Answers3

2

You could define a LoopBreak exception, raise that in a two function, and catch it in the loop to break:

class LoopBreak(Exception):
    pass    

def zero():
    print("Yo")

def one():
    print("Hey")

def two():
    raise LoopBreak

options = {
    0: zero,
    1: one,
    2: two
}

while True:
    response = int(input("Number: "))
    try:
        options.get(response)()
    except TypeError:
        print("Not a valid response")
    except LoopBreak:
        break

As a point of interest, this is similar to the pattern used natively by Python to stop generators; they raise a StopIteration exception when they run out of values to yield.

EDIT: As @mfripp correctly notes below, this will mask any TypeErrors that are raised during execution of zero or one. I would change the main loop to this instead (so you don't have to rely on TypeError):

while True:
    response = int(input("Number: "))
    action = options.get(response)
    if action is None:
        print("Not a valid response")
        continue
    try:
        action()
    except LoopBreak:
        break
BingsF
  • 1,269
  • 10
  • 15
1

There are a few ways you could do this.

Here's a more robust version of @BingsF's clever answer (this one won't mask exceptions raised within the selected function):

class LoopBreak(Exception):
    pass    

def zero():
    print("Yo")

def one():
    print("Hey")

def two():
    raise LoopBreak

options = {
    0: zero,
    1: one,
    2: two
}

while True:
    try:
        response = int(input("Number: "))
        action = options[response]
    except (ValueError, KeyError):
        print("Not a valid response")
        continue

    try:
        action()
    except LoopBreak:
        break

Or you could specify a special flag in your dictionary that will force a break:

def zero():
    print("Yo")

def one():
    print("Hey")

options = {
    0: zero,
    1: one,
    2: False
}

while True:
    try:
        response = int(input("Number: "))
        action = options[response]
    except (ValueError, KeyError):
        print("Not a valid response")
        continue

    if action is False:
        break
    else:
        action()

Or use a special return value to force a break:

def zero():
    print("Yo")
    # returns None by default

def one():
    print("Hey")
    # returns None by default

def two():
    return False

options = {
    0: zero,
    1: one,
    2: two
}

while True:
    try:
        response = int(input("Number: "))
        action = options[response]
    except (ValueError, KeyError):
        print("Not a valid response")
        continue

    if action() is False:
        break

The following code might be more "Pythonic". It could be infinitesimally slower than the approaches above, because it has to check all the if statements instead of looking up the function in a dictionary via a hash. But it may be easier to read and maintain.

while True:
    try:
        response = int(input("Number: "))
    except ValueError:
        response = -1    # error flag

    if response == 0:
        print("Yo")
    elif response == 1:
        print("Hey")
    elif response == 2:
        break
    else:
        print("Not a valid response")
Matthias Fripp
  • 17,670
  • 5
  • 28
  • 45
0

This is all you need:

while True:
    response = int(input("Number: "))
    if response not in options:
        print("Not a valid response")
    else if response == 2:
        break
    else:
        options[response]()  # invoke the corresponding function

Incidentally, storing functions in a dictionary and having to invoke them like this isn't exactly Pythonic. It's a lot nicer to simply explicitly enumerate the behaviour you need with successive ifs.

Akshat Mahajan
  • 9,543
  • 4
  • 35
  • 44
  • Yes, that part works, but I'm looking for a way to exit the loop. – MANA624 Aug 20 '16 at 03:05
  • Okay thank you. Do you think that I should just ditch the dictionary and stick with a bunch of if and elif statements? – MANA624 Aug 20 '16 at 03:08
  • 1
    @MANA624 I find in practice that if-else just makes reading code easier. Using a dictionary is usually best for _data_, and you can still use a dictionary for storing functions, but I've found in practice that explicit enumeration is actually a really nice thing in terms of code maintenance. – Akshat Mahajan Aug 20 '16 at 03:09
  • @MANA624 If you have a _lot_ of defined functions, using a dictionary is not a bad option. Whatever works best for your perceived use case (including readability and long-term maintenance) should be fine. – Akshat Mahajan Aug 20 '16 at 03:10