Dictionaries and default values

Assuming connectionDetails is a Python dictionary, what's the best, most elegant, most "pythonic" way of refactoring code like this?

if "host" in connectionDetails: host = connectionDetails["host"] else: host = someDefaultValue 

9 Answers

Like this:

host = connectionDetails.get('host', someDefaultValue) 
10

You can also use the defaultdict like so:

from collections import defaultdict a = defaultdict(lambda: "default", key="some_value") a["blabla"] => "default" a["key"] => "some_value" 

You can pass any ordinary function instead of lambda:

from collections import defaultdict def a(): return 4 b = defaultdict(a, key="some_value") b['absent'] => 4 b['key'] => "some_value" 
3

While .get() is a nice idiom, it's slower than if/else (and slower than try/except if presence of the key in the dictionary can be expected most of the time):

>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", ... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10") 0.07691968797894333 >>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", ... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10") 0.4583777282275605 >>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", ... stmt="a=d.get(1, 10)") 0.17784020746671558 >>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", ... stmt="a=d.get(2, 10)") 0.17952161730158878 >>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", ... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10") 0.10071221458065338 >>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", ... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10") 0.06966537335119938 
10

For multiple different defaults try this:

connectionDetails = { "host": "" } defaults = { "host": "127.0.0.1", "port": 8080 } completeDetails = {} completeDetails.update(defaults) completeDetails.update(connectionDetails) completeDetails["host"] # ==> "" completeDetails["port"] # ==> 8080 
1

This is not exactly the question asked for but there is a method in python dictionaries: dict.setdefault

 host = connectionDetails.setdefault('host',someDefaultValue) 

However this method sets the value of connectionDetails['host'] to someDefaultValue if key host is not already defined, unlike what the question asked.

1

(this is a late answer)

An alternative is to subclass the dict class and implement the __missing__() method, like this:

class ConnectionDetails(dict): def __missing__(self, key): if key == 'host': return "localhost" raise KeyError(key) 

Examples:

>>> connection_details = ConnectionDetails(port=80) >>> connection_details['host'] 'localhost' >>> connection_details['port'] 80 >>> connection_details['password'] Traceback (most recent call last): File "python", line 1, in <module> File "python", line 6, in __missing__ KeyError: 'password' 
1

Testing @Tim Pietzcker's suspicion about the situation in PyPy (5.2.0-alpha0) for Python 3.3.5, I find that indeed both .get() and the if/else way perform similar. Actually it seems that in the if/else case there is even only a single lookup if the condition and the assignment involve the same key (compare with the last case where there is two lookups).

>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", .... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10") 0.011889292989508249 >>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", .... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10") 0.07310474599944428 >>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", .... stmt="a=d.get(1, 10)") 0.010391917996457778 >>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", .... stmt="a=d.get(2, 10)") 0.009348208011942916 >>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", .... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10") 0.011475925013655797 >>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", .... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10") 0.009605801998986863 >>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", .... stmt="if 2 in d:\n a=d[2]\nelse:\n a=d[1]") 0.017342638995614834 

You can use a lamba function for this as a one-liner. Make a new object connectionDetails2 which is accessed like a function...

connectionDetails2 = lambda k: connectionDetails[k] if k in connectionDetails.keys() else "DEFAULT" 

Now use

connectionDetails2(k) 

instead of

connectionDetails[k] 

which returns the dictionary value if k is in the keys, otherwise it returns "DEFAULT"

1

I am sure that all these answers are ok but it shows that there is no 'nice' way of doing this. I use dictionaries instead of case statements all the time and to add a default clause I just call the following function:

def choose(choise, choises, default): """Choose a choice from the choises given """ return choises[choise] if choise in choises else default 

That way I can use normal dictionaries without special default clauses etc.

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like