[Tutor] Given a string, call a function of that name
Peter Otten
__peter__ at web.de
Wed May 17 02:42:40 EDT 2017
boB Stepp wrote:
> My son (Now 13 years old.) is merrily programming away in Python 3 (Of
> course!) and has many projects he is working on. Today he resumed
> work on his efforts to create a natural language parser, which he
> hopes will enable people to type in commands to a DnD-like game using
> natural English. An ambitious project to be sure! He asked me if
> there was any way that if he identified a word in the user's input
> corresponding to one of his functions or methods, if he could use that
> word to run a function of the same name. I said I had done something
> once where I used the word as a dictionary key, which was then used to
> call the function. After fumbling around a bit I came up with the
> following program for him to play around with:
>
> ==========================================================================
> #!/usr/bin/env python3
>
> def spam(phrase):
> print('spam function said: ', phrase)
>
> def ham(phrase):
> print('ham function said: ', phrase)
>
> def eggs(phrase):
> print('eggs function said: ', phrase)
>
> def get_input():
> function = input('Which do you want: spam, ham or eggs?\n')
> phrase = input('\nHow do you want your food to be prepared?\n')
> return function, phrase
>
> def check_fcn_input(function):
> valid_fcns = ['spam', 'ham', 'eggs']
> if function in valid_fcns:
> return True
> else:
> return False
>
> def run_fcn(function, phrase):
> fcn_dict = {'spam': spam, 'ham': ham, 'eggs': eggs}
> fcn_dict[function](phrase)
>
> def main():
> function, phrase = get_input()
> while not check_fcn_input(function):
> print("You made an invalid food choice! Let's try this again!")
> function, phrase = get_input()
> run_fcn(function, phrase)
>
> if __name__ == '__main__':
> main()
> ==========================================================================
>
> This works, but I cannot but help wondering if there is a more direct
> approach? Given the above three functions spam(), ham() and eggs(),
> and given a string 'ham', what is the quickest, most direct way to run
> that function? Or similarly for the other two?
If you are not bothered about security, getattr(module, function_name)() or
even eval(). But usually the approach you have chosen is preferrable.
> Oh, and I suppose I should ask for a critique of the code as written
> for appropriate Python style, proper targeted function use, etc. I am
> always eager to learn!
> if function in valid_fcns:
> return True
> else:
> return False
should really be spelt
return function in valid_fcns
and personally I wouldn't mind to type the few extra chars to make it
return function in valid_functions
;)
> However, I did not use doc strings for the
> functions or write tests. If I were actually planning on using this
> for real, I would have done so.
>
> Hmm. It bothers me that in check_fcn_input() I have a list valid_fcns
> and in run_fcn() I have a dictionary fcn_dict. These contain
> essentially the same information. Would this be a case for a global
> function dictionary (Which I could also use to check for valid
> functions.) or perhaps a class which only exists to have this function
> dictionary?
A global is indeed better than the duplicate information in your list and
dict. Here's another option, return the function instead of information
about its existence:
def find_function(function):
fcn_dict = {'spam': spam, 'ham': ham, 'eggs': eggs}
return fcn_dict.get(function)
def main():
while True:
function_name, phrase = get_input()
function = find_function(function_name)
if function is not None:
break
print("You made an invalid food choice! Let's try this again!")
function(phrase)
If want to try the class-based approach you can steal from the cmd module:
class Lookup:
prefix = "do_"
def do_spam(self, phrase):
print('spam function said: ', phrase)
def do_ham(self, phrase):
print('ham function said: ', phrase)
def do_eggs(self, phrase):
print('eggs function said: ', phrase)
def known_names(self):
offset = len(self.prefix)
return sorted(
name[offset:] for name in dir(self)
if name.startswith(self.prefix)
)
def get_input(self):
names = self.known_names()
names = ", ".join(names[:-1]) + " or " + names[-1]
function = input('Which do you want: {}?\n'.format(names))
phrase = input('\nHow do you want your food to be prepared?\n')
return function, phrase
def find_function(self, function):
return getattr(self, self.prefix + function, None)
def one_cmd(self):
while True:
function_name, phrase = self.get_input()
function = self.find_function(function_name)
if function is not None:
break
print("You made an invalid food choice! Let's try this again!")
function(phrase)
def main():
main = Lookup()
main.one_cmd()
The name mangling with the do_-prefix prevents that the user can invoke
arbitrary methods.
More information about the Tutor
mailing list