0

In Python I want to use user input as a function call. In particular, I have the following code I'm testing out:

def forward(forwardValue):
    print("in forward, with value " + forwardValue)
    return

command = 'forward'  # this would come in as user input
value = '255'
command(value)

What I would like to see is a printout that says: in forward, with value 255

What I do see is an error that:

'str' object is not callable

That error message makes sense, and I can see why what I tested doesn't work, but is there any way to do what I want to do? In other words, can user input be used as a function call?

What I do currently instead is use an "if" statement to check if the user input is the 'forward' statement, and if so to call the function 'forward'. But I would like to be more Pythonesque if possible.

I have a list of about 30 different commands the user can use, each of which commands has a defined function to handle it. My series of 30 "if" and "elif" statements seems clumsy.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Daanii
  • 259
  • 2
  • 3
  • 12
  • 3
    You need a *mapping*, e.g. a dictionary `{'forward': foward}`. – jonrsharpe Jun 24 '15 at 20:47
  • I would suggest going through a [Python Tutorial](https://developers.google.com/edu/python/) to learn some basics. – skrrgwasme Jun 24 '15 at 20:57
  • The Python Tutorial does look helpful. But I'm taking the excellent EdX class "Introduction to Programming with Python" that MIT offers, and the teacher's examples got me thinking of a different approach in my own program than I was currently taking. Sorry if my question seemed too naive, but I did try to figure it out on my own before asking. – Daanii Jun 24 '15 at 21:28

2 Answers2

2

Quick and dirty solution

Use globals():

globals()[command](value)

Wiser approach

However, if security is a concern, you may want to limit what can be called from user input. For example define a dictionary of callable functions:

functions = {'forward': forward, 'othercommand': othercommand}

or combine with previous solution to avoid repetitions:

functions = {k: globals()[k] for k in ('forward', 'othercommand')}

Then you would call your function with:

functions[command](value)

Alternative approach

You can use a class as namespace for your user-callable functions:

class Functions:
    @staticmethod
    def forward(arg):
        print("in forward, with value " + forwardValue)
    @staticmethod
    def othercommand():
        pass

(if you are on Python 3.x, @staticmethod is optional in this context)

then access the static methods with getattr():

getattr(Functions, command)(value)
fferri
  • 18,285
  • 5
  • 46
  • 95
  • Those functions grouped in the class should be `@staticmethod`s. – Lukas Graf Jun 24 '15 at 20:59
  • Also, security is by far not the only reason not to use `globals()`: It makes your code hard to read, hard to reason about, and a nightmare to refactor. Want to rename the `forward()` function? Have fun tracking down references to it if you're using `globals()`. – Lukas Graf Jun 24 '15 at 21:01
  • it is not needed if used in this way (i.e. the class Functions is never instantiated) – fferri Jun 24 '15 at 21:01
  • for Python 2.x it is necessary, yes. – Lukas Graf Jun 24 '15 at 21:04
  • right. I don't use Python 2.x since 6 years or so... I'll correct my answer – fferri Jun 24 '15 at 21:07
1

Daanii, what seems to be catching you is that the variable named command contains the string 'forward'. This string object is not your function. Using an if statement, as you said you are already doing, is the most straightforward way to go about it.

If you have many functions and/or you don't necessarily want the function name and the user-passed string to match, then I would suggest using a dictionary.

d = {'foward':forward, 'backward':backward, 'flurb':durf}

def forward(forwardValue):
    print("in forward, with value " + forwardValue)
    return

def backward(backwardValue):
    print("in backward, with value " + backwardValue)
    return

def durf(value):
    print("in durf, with value " + value)
    return

try:
    command = raw_input('Enter your thing:\n')
    value = '255'
    d[command](value)
except KeyError:
    print 'The user did not enter a valid string'
Tom
  • 1,196
  • 2
  • 12
  • 29