112

I have a str object for example: menu = 'install'. I want to run install method from this string. For example when I call menu(some, arguments) it will call install(some, arguments). Is there any way to do that ?

Julien
  • 13,986
  • 5
  • 29
  • 53
İlker Dağlı
  • 1,593
  • 3
  • 13
  • 10
  • I changed the duplicate link because https://stackoverflow.com/questions/3061/ is narrowly scoped; this question is looking for `install` in an unspecified namespace (but probably intended to be either the local or global namespace), not as an attribute of a module. – Karl Knechtel Jul 06 '22 at 23:12

3 Answers3

152

If it's in a class, you can use getattr:

class MyClass(object):
    def install(self):
          print "In install"

method_name = 'install' # set by the command line options
my_cls = MyClass()

method = None
try:
    method = getattr(my_cls, method_name)
except AttributeError:
    raise NotImplementedError("Class `{}` does not implement `{}`".format(my_cls.__class__.__name__, method_name))

method()

or if it's a function:

def install():
       print "In install"

method_name = 'install' # set by the command line options
possibles = globals().copy()
possibles.update(locals())
method = possibles.get(method_name)
if not method:
     raise NotImplementedError("Method %s not implemented" % method_name)
method()
Sam Dolan
  • 31,966
  • 10
  • 88
  • 84
  • 1
    Thank you for your answer. But what if the method is not in a class? – İlker Dağlı Oct 29 '11 at 02:23
  • Thank you so much sdolan. I have tried with **globals** not **locals** and it works. – İlker Dağlı Oct 29 '11 at 02:31
  • 6
    The latter does not .copy() globals before mutating it, inviting all sorts of trouble. And it has a bug, since it calls the method immediately before checking it, then calls its result again. Also, it's common practise to use a prefix, to prevent calling just ANY element in the namespace (e.g. "do_install()"). – pyroscope Oct 29 '11 at 05:18
  • @pyroscope: Good catch on the globals. I've updated the sample code to do that. I also agree on the prefix, and do that in my own code where it makes sense. I don't know the specifics of the OPs problem, so I'm trying to not jump to any conclusions. – Sam Dolan Oct 30 '11 at 02:27
  • @sdolan , Perfect answer but a minor mistake getattr does not respond with None if attribute does not exista rather it raises an exception so **if not method: raise Exception("Method %s not implemented" % method_name)** is not needed – Sarath Sadasivan Pillai Feb 09 '16 at 10:56
  • @Sarathsp: haha good catch. Only took 4.5 years for someone to catch it ;) – Sam Dolan Feb 20 '16 at 20:31
  • And if you are sure that method exists, you can use: globals().get(method_name)() or locals().get(method_name)() – Alex Benfica Apr 24 '16 at 11:38
  • Your first answer: raise NotImplementedError("Class `{}` does not implement `{}`".format(my_cls.__class__.__name__, method_name) Should be: raise NotImplementedError("Class `{}` does not implement `{}`".format(my_cls.__class__.__name__, method_name)) – Mo Ali Jan 18 '17 at 19:30
  • @MoAli cool, just updated to add the extra paren. – Sam Dolan Jan 18 '17 at 20:17
  • @AlexBenfica what's the difference between calling by globals and locals? – Nihat Aug 17 '20 at 11:35
  • 1
    @Nihat please refer to this question. https://stackoverflow.com/questions/7969949/whats-the-difference-between-globals-locals-and-vars – Alex Benfica Aug 17 '20 at 19:39
  • Where should i import NotImplementedError – alper Nov 20 '21 at 13:53
76

You can use a dictionary too.

def install():
    print "In install"

methods = {'install': install}

method_name = 'install' # set by the command line options
if method_name in methods:
    methods[method_name]() # + argument list of course
else:
    raise Exception("Method %s not implemented" % method_name)
concentricpuddle
  • 777
  • 5
  • 11
  • 11
    I believe using dictionary is a bit more clean that relying on globals().copy() in accepted answer. – Victor Farazdagi Jan 22 '13 at 02:09
  • 1
    @AgnivaDeSarker Make sure that in setting up your dictionary, you haven't called the function - i.e., that you use only the function name, with no brackets: `{'install': install}` – Hannele Apr 25 '13 at 14:31
  • I like this more, as it easy to pass parameters, and you can control the list of methods easily with dictionalry – Kostanos Feb 26 '15 at 21:35
  • I would prefer this approach to the accepted answer but I can't seem to make it work with class methods. – navjotk Feb 01 '16 at 19:16
  • @navjotk in the declaration of your dictionary: `methods = {'install': self.install}` (or `obj.install`) – Ohad Cohen Oct 06 '16 at 14:38
  • hmm, i tried something similar here but it wasn't working: https://stackoverflow.com/questions/50359442/apply-a-method-from-a-list-of-methods-to-pandas-dataframe – tired May 15 '18 at 22:05
  • If using a dictionary to store the method, the method **must** be defined before the dictionary - as in the method definition must be above the line the dictionary is on or you will get; _"NameError: name 'X' is not defined'_ – myol Nov 26 '18 at 06:51
40

Why cant we just use eval()?

def install():
    print "In install"

New method

def installWithOptions(var1, var2):
    print "In install with options " + var1 + " " + var2

And then you call the method as below

method_name1 = 'install()'
method_name2 = 'installWithOptions("a","b")'
eval(method_name1)
eval(method_name2)

This gives the output as

In install
In install with options a b
Renaud
  • 16,073
  • 6
  • 81
  • 79
Husain Khambaty
  • 740
  • 1
  • 9
  • 22
  • He is asking for a way to call the function with arguments. Can you detail? – Mikaël Mayer Apr 25 '13 at 13:22
  • 3
    Yup it works with arguments as well in the same manner. – Husain Khambaty Apr 25 '13 at 14:21
  • 6
    If you are using the above-mentioned strategy, it means that you are dynamically defining your methods/functions, in other words, they can be a lot of things, including malicious code. I suggest you [this article](http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html), and then, never using eval again. When needed, just use `json`. – lucastamoios Jan 16 '17 at 12:03
  • @lucastamoios: Not true at least in case of OP - using predefined strings as function names. – Fr0zenFyr Feb 28 '18 at 12:31
  • How do you suggest building the argument list for an arbitrary list of arguments with `installWithOptions`? Showing the hard coded example certainly doesn't tell the story here. This is all of course ignoring how horrible of a practice using eval is in the first place. I've never seen a legitimate use case for it – Cruncher Mar 20 '18 at 22:01
  • eval() is safe to use if the input can be trusted – Justas Mar 30 '18 at 21:28
  • 1
    @Justas Sure. In theory. But now that code is there forever, and a change that seems harmless in another file now turns out to open a giant security hole because of the use of an eval statement. You should build around as few assumption as possible. Because who knows when they'll change. – Cruncher Apr 03 '18 at 13:27
  • 2
    `eval(method_name1)(a,b)` also works. Fair point about safety-- ok for tiny private scripts but do they stay that way? Take great care if the string is coming from a website, for example. It isn't `eval` that's the only problem here-- using `getattr` on a string from an arbitrary source is just as dangerous. – Nic Nov 15 '18 at 02:23
  • **Do not ever use `eval` on data that could possibly ever come from outside the program. It is a security hole that allows whoever creates that data to run arbitrary Python code on your computer. No, you cannot practically sandbox it.** – Karl Knechtel Aug 17 '22 at 07:53