How to handle integer and string from input

It’s not very long since I joined the SO community, but even in my short experience I could see that from time to time ir comes a guy trying to build some kind of dungeon/room-based super game in Python without any Python skills.

Some time ago I gave an asnwer to someone who seemed to coming straight off php. His Python script was a simple “print a menu and ask at the user what to do”. It wasn’t a game, but the core it’s similar and maybe it could of interest to somebody.

He was having problems handling both integers and strings (that’s where the title came from).
And this was my answer (ported in python-3.x):

Let me answer your question with another question:
Is it really necessary to mix letters and numbers?
Can’t they just be all strings?

Well, let’s take the long way and see what the program is doing:

  1. Display the main menu
  2. Ask/receive the user input
    • If is valid: ok
    • If not: print an error message and repeat
  3. Now we have a valid input
    • If is a letter: do a special tasks
    • If is a number: call the right draw-function

Point 1. Let’s make a function for this:

def display_menu():
    menu_text = """\
  Draw a Shape
  ============

  1 - Draw a triangle
  2 - Draw a square
  D - Display what was drawn
  X - Exit"""
    print(menu_text)

display_menu is very simple so there’s no need to explain what it does, but we’ll see later the advantage of putting this code into a separate function.

Point 2. This will be done with a loop:

options = ['1', '2', 'D', 'X']

while 1:
    choice = input('  Enter your choice: ')
    if choice in options:
        break
    else:
        print('Try Again!')

Point 3. Well, after a second thought maybe the special tasks are not so special, so let’s put them too into a function:

def exit():
    """Exit"""  # this is a docstring we'll use it later
    return 0

def display_drawn():
    """Display what was drawn"""
    print('display what was drawn')

def draw_triangle():
    """Draw a triangle"""
    print('triangle')

def draw_square():
    """Draw a square"""
    print('square')

Now let’s put it all together:

def main():
    options = {'1': draw_triangle,
               '2': draw_square,
               'D': display_drawn,
               'X': exit}

    display_menu()
    while 1:
        choice = input('  Enter your choice: ').upper()
        if choice in options:
            break
        else:
            print 'Try Again!'

    action = options[choice]   # here we get the right function
    action()     # here we call that function

The key to your switch lies in options that now is no more a list but a dict, so if you simply iterate on it like if choice in options your iteration is on the keys:['1', '2', 'D', 'X'], but if you do options['X'] you get the exit function (isn’t that wonderful!).

Now let’s improve again, because maintaining the main menu message and the options dictionary it’s not too good, a year from now I may forget to change one or the other, I will not get what I want and I’m lazy and I don’t want to do twice the same thing, etc…

So why don’t pass the options dictionary to display_manu and let display_menu do all the work using the doc-strings in __doc__ to generate the menu:

def display_menu(opt):
    header = """\
  Draw a Shape
  ============

"""
    menu = '\n'.join('{} - {}'.format(k,func.__doc__) for k,func in opt.items())
    print(header + menu)

We’ll need OrderedDict instead of dict for options, because OrderedDict as the name suggests keep the order of its items (take a look at the official doc). So we have:

def main():
    options = OrderedDict((('1', draw_triangle),
                           ('2', draw_square),
                           ('D', display_drawn),
                           ('X', exit)))

    display_menu(options)
    while 1:
        choice = input('  Enter your choice: ').upper()
        if choice in options:
            break
        else:
            print('Try Again!')

    action = options[choice]
    action()

Beware that you have to design your actions so that they all have the same signature (they should be like that anyway, they are all actions!). You may want to use callables as actions: instances of class with __call__ implemented. Creating a base Action class and inherit from it will be perfect here.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: