Search code examples
pythondictionarydefault

Is it okay to nest a dict.get() inside another or is this bad design?


So, I am working on a code base where a dictionary contains some key information. At some point in the development process the name of one of the keys was changed, but the older key still exists in a lot of places. Lets call the keys new and old for reference.

In order to make it compatible with the older version, I am doing something like:

dict_name.get(new_key,dict_name.get(old_key,None))

Is this bad design or is it okay? Why/Why not?

Example for clarification: (Based on input by @Alexander)

There are two dictionaries d1 and d2.

d1={k1:v1,old_key:some_value}
d2={k1:v1,new_key:some_value}

The function which I am designing right now could get either d1 or d2 like dictionary as an argument. My function should be able to pick up some_value, regardless of whether old_key or new_key is present.


Solution

  • That is a reasonable approach. The only downside is that it will perform the get for both keys, which will not affect performance in most situations.

    My only notes are nitpicks:

    • dict is a reserved word, so don't use it as a variable
    • None is the default, so it can be dropped for old_key, e.g.:
    info.get('a', info.get('b'))
    

    In response to "Is there a way to prevent the double call?": Yup, several reasonable ways exist =).

    1. The one-liner would probably look like:

      info['a'] if 'a' in info else info.get('b')

      which starts to get difficult to read if your keys are longer.

    2. A more verbose way would be to expand it out into full statements:

      val = None
      if 'a' in info:
          val = info['a']
      elif 'b' in info:
          val = info['b']
      
    3. And finally a generic option (default after *keys) will only work with python 3):

      def multiget(info, *keys, default=None):
          ''' Try multiple keys in order, or default if not present '''
          for k in keys:
              if k in info:
                  return info[k]
          return default
      

      which would let you resolve multiple invocations cleanly, e.g.:

      option_1 = multiget(info, 'a', 'b')
      option_2 = multiget(info, 'x', 'y', 'z', default=10)
      

    If this is somehow a pandemic of multiple api versions or something (?) you could even go so far as wrapping dict, though it is likely to be overkill:

    >>> class MultiGetDict(dict):
    ...   def multiget(self, *keys, default=None):
    ...       for k in keys:
    ...           if k in self:
    ...               return self[k]
    ...       return default
    ... 
    >>> d = MultiGetDict({1: 2})
    >>> d.multiget(1)
    2
    >>> d.multiget(0, 1)
    2
    >>> d.multiget(0, 2)
    >>> d.multiget(0, 2, default=3)
    3