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.

_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor

Reply via email to