1

I am creating a python shell script that will allow the user to enter commands that output a string. But also some commands will call a function, like help, or exit I know python does not have a switch case so I am using a dictionary instead, as I do not want to use if and else statements

The problem:

The problem is I cannot use a function call inside the dictionary with the way I have it implemented as it expects to output a string, so when I enter help I get an error

    #!/usr/bin/python

    x = 0
    while x == 0:



        def helpCommand():
            print "this will help you"

        switcher = {
           "url": " the url is: ... ",
           "email": " my email is: ...",
           "help": helpCommand,
           "exit": exit,
        }

        choice = raw_input("enter your command choice: ")

        def outputChoice(choice)
            return switcher.get(choice)

        print outputChoice(choice)

I tried to solve this problem by using function call instead, but now I get an error when trying to call a string

 def outputChoice(choice)
                    return switcher[choice]()

TypeError: 'str' object is not callable

How to fix this problem ?

Mohamad Zein
  • 777
  • 1
  • 8
  • 33
  • 2
    If it's a string, you can't "call" it. If it's a function, you *must* call it. You'll need an `if..else` to figure out which to do. – deceze Feb 02 '16 at 10:38
  • 1
    You may find the built-in `callable()` useful: https://docs.python.org/2/library/functions.html#callable – Djizeus Feb 02 '16 at 10:39
  • 2
    I'd just make all the values in `switcher` functions. Then just call them. They can all take the same arguments (if any). If you really want to have strings as well as functions, you can always check the type and handle strings separately, but I don't think that buys you much. – Tom Karzes Feb 02 '16 at 10:46
  • look here http://stackoverflow.com/questions/3061/calling-a-function-of-a-module-from-a-string-with-the-functions-name-in-python Possible dublicate? – Stefan Reinhardt Feb 02 '16 at 10:46

2 Answers2

3

Djizeus's suggestion of callable() makes this simple.

Your code organization is a bit odd. You shouldn't normally put function definitions inside a loop, since that causes the function to be redefined on every loop, which is pointless and inefficient.

Here's a modified version of your code, using Djizeus's suggestion.

#!/usr/bin/python

def helpCommand():
    print "this will help you"

switcher = {
    "url": " the url is: ... ",
    "email": " my email is: ...",
    "help": helpCommand,
    "exit": exit,
}

def process_choice(choice):
    item = switcher[choice]
    if callable(item):
        item()
    else:
        print item

x = 0
while not x:
    choice = raw_input("Enter your command choice: ")
    process_choice(choice)
    #other stuff that modifies `x`

If nothing in the loop actually modifies x, you can just get rid of that and make the loop into:

while True:
    choice = raw_input("Enter your command choice: ")
    process_choice(choice)
PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
1

You can try to call the function, and if it fails, print the string. iirc this is the pythonic way - it is easier to ask for forgiveness then for permission (or in this case, the callable attribute).

def bar():
    print 'foo'

gru = { 'dead': 'beef',
        'foo' : bar}

for what in [ 'dead', 'foo']:
    try:
        gru[what]()
    except TypeError:
        print gru[what]
hecke
  • 236
  • 1
  • 6
  • EAFP is great. And exception handling in Python is very efficient: it's faster than the equivalent `if`-based code when the exception isn't raised. However, it's _considerably_ slower if the exception _is_ raised. And in this case, it looks like the simple string values in the OP's `switcher` dict will be used more often than the function values, so using `try:... except` is probably not a good idea here. – PM 2Ring Feb 02 '16 at 11:05
  • by the ratio of functions/strings - you are right. but this is an interface used by human beings so I would not expect any remarkable latency. if you would go for performance, you could/should avoid the check for a callable at all by defining an output-function `def _outp(str)` and put all strings in the dict using this function, eg. `'mail' : _outp('my mail is..')` - but this is even uglier :) – hecke Feb 02 '16 at 11:14
  • Fair point. Arguing about the speed of a routine that's mostly just sitting there waiting for terminal input is a bit silly. :) But in general, my remarks are valid. IME, if the exception is raised more than about 5% of the time then using `if` is faster. – PM 2Ring Feb 02 '16 at 11:19