0

I'm learning Python. I'm reading some code containing something like this:

class Menu:
    '''Display a menu and respond to choices when run.'''
    def __init__(self):
        self.notebook = Notebook()
        self.choices = {
            "1": self.show_notes,
            "2": self.search_notes,
            "3": self.add_note,
            "4": self.modify_note,
            "5": self.quit
            }

    def display_menu(self):
        print("""
Notebook Menu

1. Show all Notes
2. Search Notes
3. Add Note
4. Modify Note
5. Quit
""")

    def run(self):
        """Display the menu and respond to choices."""
        while True:
            self.display_menu()
            choice = input("Enter an option: ")
            action = self.choice.get(choice)
            if action:
                action()
            else:
                print("{0} is not a valid choice".format(choice))

def show_notes(self):
    pass

def search_notes(self):
    pass

def add_note(self):
    pass

def modify_note(self):
    pass

def quit(self):
    pass

There are some lines very interesting:

action = self.choice.get(choice)
if action:
    action()

Seems it's creating a temporary name for a specific function. So I did the following test for it to learn more:

>>> def show_notes():
    print("show notes")

>>> def search_notes():
    print("search notes")

>>> choice = {"1": show_notes, "2": search_notes}
>>> action = choice.get(1)
>>> action()

But I get the following error:

Traceback (most recent call last):
  File "<pyshell#64>", line 1, in <module>
    action()
TypeError: 'NoneType' object is not callable

Can someone tell me what the technique is and what principle is behind?

0

3 Answers 3

5

Functions are first class objects, and you can create additional references to them. These are just as temporary as you need to them to be, but they can be permanent too.

Your own attempt confused strings and integers however; you used 1 (an integer) where the actual key is '1' (a string). Because you used the wrong key, the dict.get() method returned a default instead, None. None is not a function object and the call fails.

Had you used the right key your code would have worked too:

>>> def show_notes():
...     print("show notes")
... 
>>> def search_notes():
...     print("search notes")
... 
>>> choice = {"1": show_notes, "2": search_notes}
>>> choice['1']
<function show_notes at 0x10b1fae18>
>>> choice['1']()
show notes

You can make use of dict.get() returning a default here too, by giving the method a better default to return:

>>> choice.get('none-such-key', search_notes)()
search notes
Sign up to request clarification or add additional context in comments.

Comments

2

It seems there's an error in your test. You should be getting "1" and not 1. Getting 1 is returning None because there's nothing defined for key 1. Therefore when you call it like a function it's not valid.

To clarify, "1" is a string and 1 is an integer, which are different keys.

Example:

>>> a = {"1": "yes"}
>>> a.get(1)
>>> a.get("1")
'yes'

Example II (using function):

>>> def hello():
...     print "hello"
...
>>> hello()
hello
>>> a = {"1": hello}
>>> b = a.get(1)
>>> b()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable
>>> b = a.get("1")
>>> b()
hello

Comments

1

It's not creating a random name for a function. The class method choice is selecting a function and returning it, and it is subsequently being assigned to the variable action. The function is then called by calling action, like you would any function.

Here's an example:

def foo():
    print(5)

def getFunction():
    return foo

x = getFunction()

x()

The output from this will be 5.

Taking a step back from all of this, you can assign any object to any variable. So consider the following example (I think this will help you understand a little bit more):

def foo():
    print(5)

bar = foo
foo = 5

foo()

This will produce an error along the lines of integer objects are not callable. The way this works is that the function object contained in foo is being assigned to variable bar, and the integer 5 is being assigned to foo. The function hasn't changed, but the variable containing it has.

The very first part of defining a function def foo is letting the interpreter know that you are defining a function object and storing in the variable foo. The name and the mechanics of the function are separate.

Does this make sense?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.