1

I've got some code that looks like the following:

if command == 'a':
    do_a(a, b, c)
elif command == 'b':
    do_b(a, b, c)
elif command == 'c':
    do_c(a, b, c)

How can I replace this type of thing with something more elegant? Perhaps, something along the lines of do_[command](a, b, c) where the function that is called is dependent upon the command?

Is it even possible?

james_dean
  • 1,477
  • 6
  • 26
  • 37
  • You may want to learn more about Python closures http://getpython3.com/diveintopython3/generators.html and `lambda` and Python `eval` http://docs.python.org/3.1/library/functions.html#eval (but don't abuse of `eval`) – Basile Starynkevitch Feb 07 '13 at 11:19
  • Something like `eval('do_'+command)(a,b,c)`. – Anton Kovalenko Feb 07 '13 at 11:19
  • 8
    Sounds like a job for a dict of functions. Avoid `eval` if at all possible. – Wooble Feb 07 '13 at 11:19
  • 1
    Don't use `eval` for this. Eval is useful for code generation (dynamically defining functions/classes) in things like the `namedtuple()` implementation where building a class using a low-level API would be needlessly complex and obscure the intent of the code. Here you're not dynamically defining anything, and `eval` would make the intent less clear. – millimoose Feb 07 '13 at 11:26
  • **Do not ever use `eval` for any data that could possibly ever come, in whole or in part, from outside of the program. It is a security hole that allows the author of that data to run arbitrary Python code on your computer. No, you cannot properly sandbox it.** – Karl Knechtel Aug 17 '22 at 08:03

5 Answers5

2

You can store commands in a dict and lookup when needed:

In [15]: commands = {'mul': lambda x,y: x*y,
                     'add': lambda x,y: x+y}

In [16]: commands['mul'](3,4)
Out[16]: 12

In [17]: commands['add'](3,4)
Out[17]: 7

In [18]: command = 'add'; vars = (4,5)
In [19]: commands[command](*vars)
Out[19]: 9

you should check whether a command indeed is in commands:

if command in commands:
    commands[command]()
else:
    # handle error
tzelleke
  • 15,023
  • 5
  • 33
  • 49
1

You could do something like that using "reflection", calling the function by string name, like explained here:

Python: call a function from string name

BUT

That wouldn't be more elegant, it would be less readable, and easier to screw up if you don't have absolute control over what's passed as command.

Your version is just fine:

Explicit is better than implicit.

Simple is better than complex.

If you really want to avoid the elif's I would go with the dict of functions approach that was suggested in the comments.

Keep away from eval for things like this.

Community
  • 1
  • 1
pcalcao
  • 15,789
  • 1
  • 44
  • 64
1
def do(fname):
    return {'a':do_a, 'b':do_b, 'c':do_c}.get(fname)

def do_a(x,y,z): return x + y + z
def do_b(x,y,z): pass
def do_c(x,y,z): pass

Usage:

do('a')(1,2,3)
6
Zeh
  • 301
  • 2
  • 4
0

I'm waiting for a better solution from others but:

def dispatch(cmd, *args):
   globals()['do_' + cmd](*args)

def do_a(a, b, c): 

Usage:

dispatch('a', 1, 2, 3)

Obviously not robust but dispatch could make sure that the function exists.

Possibly a nice way would be to use decorators.

@command()
def do_a(a,b,c):
    ...

@command("do_x")
def myfunc(a, b, c):
   ...

Your decorator could maintain the dispatch lookup table etc.

It depends how worried you are that there might be a need to use existing functions or worry about name collisions.

sotapme
  • 4,695
  • 2
  • 19
  • 20
0

This might help you, here souce can be any generic module path ending with function name like module.funcname

So a sample call would look like convertStringToFunction(module.funcname)

def import_module(name):
    mod = __import__(name)
    components = name.split('.')
    for comp in components[1:]:
        mod = getattr(mod, comp)
return mod

def convertStringToFunction(source):
    tempArr = source.rsplit('.',1)
    mod = import_module(tempArr[0])
    func = getattr(mod, tempArr[1])
    return func
gaurav
  • 91
  • 3