Long elif chains vs dictionary with exec()
Question: I am a novice to python and I have read a lot of tutorials on how to properly
code. One thing that keep popping up is to never write the same line of code
multiple times. I am unsure if long elif arguments count for that, but to me
it just looks like bad code.

for exaple:

    
    
    class answers(object):
        def __init__(self):
            self.x = 'hello world'
    
        def knight(self):
            print('Neee!')
        def bunny(self):
            print('Rawwww!')
        def pesant(self):
            print('Witch!')
        def dingo(self):
            print('Bad, wicked, naughty Zoot!')
    foo = answers()    
    egg = input("Sounds:")
    
    if egg == "knight":
        foo.knight()
    elif egg == 'bunny':
        foo.bunny()
    elif egg == 'pesant':
        foo.pesant()
    elif egg == 'dingo':
        foo.dingo()
    else:
        print("I don't know?")
    

That works but I think the following code is looks cleaner.

    
    
    class answers(object):
        def __init__(self):
            self.x = 'hello world'
    
        def knight(self):
            print('Neee!')
        def bunny(self):
            print('Rawwww!')
        def pesant(self):
            print('Witch!')
        def dingo(self):
            print('Bad, wicked, naughty Zoot!')
    
    foo = answers()
    responce = {'knight': 'foo.knight()', 'bunny': 'foo.bunny()', 'pesant': 'foo.pesant()', 'dingo': 'foo.dingo()'}
    
    while True:
        try:
            egg = input('sounds:')
            exec(responce[egg])
        except KeyError:
            print("I don't know")
    

Both lines of code do the same thing, does it really matter which I use or is
one better then the other?

side note, I know that exec() should not normally be used but I could not find
another way to assign a function to a string.


Answer: You can assign function name to variable if you skip `()` and arguments

    
    
    responce = {
        'knight': foo.knight,
        'bunny': foo.bunny,
        'pesant': foo.pesant,
        'dingo': foo.dingo,
    }
    

And then you can run it using `()` (with expected arguments)

    
    
    responce[egg]()
    
    #responce[egg](arg1, arg2, ...) # if function require arguments
    

Full code

    
    
    class Answers(object): # CamelCase name for class - see PEP8 document
    
        def __init__(self):
            self.x = 'hello world'
    
        def knight(self):
            print('Neee!')
    
        def bunny(self):
            print('Rawwww!')
    
        def pesant(self):
            print('Witch!')
    
        def dingo(self):
            print('Bad, wicked, naughty Zoot!')
    
    foo = Answers()
    
    responce = {
        'knight': foo.knight,
        'bunny': foo.bunny,
        'pesant': foo.pesant,
        'dingo': foo.dingo,
    }
    
    while True:
        try:
            egg = input('sounds:')
            responce[egg]() # call function
        except KeyError:
            print("I don't know")
    

* * *

**BTW:** This way you can even use function name as argument for another
function.

It is use in Tkinter to assign function to Button

    
    
    Button( ..., text="knight", command=foo.knight)
    

or to assign function to event

    
    
    bind('<Button-1>', foo.knight)
    

* * *

If you need to assign function with arguments then you can use `lambda`
function.

Version for Python3:

    
    
    responce = {
        'knight': lambda:print('Neee!'),
        'bunny': lambda:print('Rawwww!'),
        'pesant': lambda:print('Witch!'),
        'dingo': lambda:print('Bad, wicked, naughty Zoot!'),
    }
    

Version for Python2:

`print` in Python2 is not a function so `lambda` will not work with `print` \-
so you have to create function.

    
    
    def show(text):
        print text
    
    responce = {
        'knight': lambda:show('Neee!'),
        'bunny': lambda:show('Rawwww!'),
        'pesant': lambda:show('Witch!'),
        'dingo': lambda:show('Bad, wicked, naughty Zoot!'),
    }
    

* * *

**EDIT:** But I would do this without functions in dictionary :)

    
    
    # --- classes ---
    
    class Answers(object):
    
        def __init__(self):
    
            # TODO: read it from file CSV or JSON
            #
            # import json 
            #
            # with open("data.json") as f: 
            #     self.data = json.load(f)
    
            self.data = {
                'knight': 'Neee!',
                'bunny': 'Rawwww!',
                'pesant': 'Witch!',
                'dingo': 'Bad, wicked, naughty Zoot!',
            }
    
        def response(self, text):
            try:
                return self.data[text]
            except KeyError:
                return "I don't know"
    
    # --- functions ---
    
      # empty
    
    # --- main ---
    
    foo = Answers()
    
    while True:
    
        egg = input('sounds: ').lower()
    
        if egg == 'exit':
            break
    
        print(foo.response(egg))
    
    # ---
    
    print("Good Bye!")
    

