python dictionary .get() method with default value throws exception even when it should not even be executed

Question:

I am having a dictionary that I would like to retrieve some values from using .get() method and if the key is not present, a default function should be executed that returns the correct value but it should only be executed if the key is not present.

I tried removing the default value and it works, so why does the function gets executed even if it won’t be used?

The SQL query will return nothing because it only contains values not already in the dict

def get_port():

    def default():
        cursor.execute("SELECT port FROM table WHERE server=%s LIMIT 1",(server_name,))
        return cursor.fetchone()[0]

    port = {
        'server1': 2222,
        'server2': 2223,
        'server3': 2224
        }
    #print(port.get(server_name)) << Works
    return port.get(server_name, default())

Error:

return cursor.fetchone()[0] TypeError: ‘NoneType’ object is not
subscriptable

I expect the default function to only execute if key is not present..

Asked By: ma7555

||

Answers:

You’re calling the function, so it does get executed (default() vs. default). (Even if you didn’t call it, i.e. .get(..., default), you’d then just get the function object back, which doesn’t suit your purposes.)

If you only want to call it when the key does not exist, you need to be slightly more verbose – and at that point, it’s worth refactoring like so:

ports = {
    "server1": 2222,
    "server2": 2223,
    "server3": 2224,
}


def get_port(server_name):
    port = ports.get(server_name)
    if not port:
        cursor.execute(
            "SELECT port FROM table WHERE server=%s LIMIT 1",
            (server_name,),
        )
        port = cursor.fetchone()[0]
    return port
Answered By: AKX

the issue with get with a default value which needs build/evaluation is that the code is executed, even if not needed, which can slow down the code, or in your case trigger unnecessary errors.

An alternative could be:

port.get(server_name) or default()

If get returns None because a key is not found, then or default() is activated. But if get returns something, it short-circuits and default() is not called.

(this could fail if get returned empty string, zero, any "falsy" value but in your case – a server name – I’ll suppose that it cannot happen)

Another option (maybe overkill here) would be to use a collections.defaultdict object

port = collections.defaultdict(default)
port.update({
        'server1': 2222,
        'server2': 2223,
        'server3': 2224
        })

Then each call to port[server] with an unknown key calls the default function and stores the result in the dictionary. Make port global or persistent and you have a created nice cache: next time the same value is fetched the default is already in the dictionary.

The previous answer will bring the solution to your current problem/question.

default() is being evaluated at first.

I want to include some more info to help you understand what is happening. The question may look naive, but it touches fundamental aspects of Python (and any programming language actually). Hopefully, that will have some interest to some readers.

The first misunderstood aspect is related to eager evaluation. Python is an eager evaluator. Here is a nice post around the topic.

Then, read about the get() method itself — it returns a value, which may be a function since functions are first class objects in Python.

Answered By: Brandt
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.