ma7555
ma7555

Reputation: 400

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

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..

Upvotes: 4

Views: 1605

Answers (3)

Jean-Fran&#231;ois Fabre
Jean-Fran&#231;ois Fabre

Reputation: 140168

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.

Upvotes: 7

Brandt
Brandt

Reputation: 5639

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.

Upvotes: 1

AKX
AKX

Reputation: 168913

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

Upvotes: 2

Related Questions