pythonmethodssetdefault

How is setdefault() method working in this invert dictionary implementation?


Could someone explain how the assignment to the dictionary "inverse" is happening in the case below?

def invert_dict(d):
  inverse = {}
  for key in d:
    new_key = d[key]
    inverse.setdefault(new_key, []).append(key)
  return inverse

letters_in_word = {"mine": 4, "yours": 5, "ours": 4, "sunday": 6, "friend": 6, "fun": 3, "happy": 5, "beautiful": 8}

print (invert_dict(letters_in_word))

The output, of course, is correct:

{8: ['beautiful'], 3: ['fun'], 4: ['mine', 'ours'], 5: ['happy', 'yours'], 6: ['sunday', 'friend']}

Python 3.x documentation says:

setdefault(key[, default]):

If key is in the dictionary, return its value. If not, insert key with a value of default and return default. default defaults to None.

Let me use an example to illustrate what I do and do not understand:

  1. Assume new_key = "happy"
  2. new_key has a value of 5
  3. setdefault() is called and let us assume 5 is already in the dictionary from "yours" (as far as I am aware since dictionaries are unordered this wouldn't necessarily be the case, but let's assume) and will return ["yours"] (My guess is that something slightly different happens here so that in fact it isn't true that "inverse.setdefault(5, [])" has returned ["yours"] and that is it)
  4. append() is called and ["yours"] --> ["yours", "happy"] - and this is what we are left with.

I know that by the end of 4 I am wrong because in fact our list has been assigned to the key "5". What I don't understand is the point at which that happened - it seems like we just returned and really should have to assign with:

inverse[new_key] = inverse.setdefault(new_key, []).append(key)

However, if I run the code like that I get the error - 'NoneType' object has no attribute 'append'.

Any explanation is appreciated - I guess I must be missing something about how the two methods are interacting.

P.S. This is my first question so apologies if the question nature/structure is not 'how things are done around here'. Let me know how to improve and I will do my best to do so!


Solution

  • Print statements are a very useful and easy way to understand what's happening in a program:

    def invert_dict(d):
        inverse = {}
        for key in d:
            new_key = d[key]
            print('key:', key)
            print('new_key:', new_key)
            print('inverse before:', inverse)
            value = inverse.setdefault(new_key, [])
            print('inverse in the middle:', inverse)
            print('value before:', value)
            value.append(key)
            print('value after:', value)
            print('inverse after:', inverse)
        return inverse
    
    letters_in_word = {"mine": 4, "yours": 5, "ours": 4, "sunday": 6, "friend": 6, "fun": 3, "happy": 5, "beautiful": 8}
    
    print(invert_dict(letters_in_word))
    

    Output:

    key: beautiful
    new_key: 8
    inverse before: {}
    inverse in the middle: {8: []}
    value before: []
    value after: ['beautiful']
    inverse after: {8: ['beautiful']}
    key: yours
    new_key: 5
    inverse before: {8: ['beautiful']}
    inverse in the middle: {8: ['beautiful'], 5: []}
    value before: []
    value after: ['yours']
    inverse after: {8: ['beautiful'], 5: ['yours']}
    key: ours
    new_key: 4
    inverse before: {8: ['beautiful'], 5: ['yours']}
    inverse in the middle: {8: ['beautiful'], 4: [], 5: ['yours']}
    value before: []
    value after: ['ours']
    inverse after: {8: ['beautiful'], 4: ['ours'], 5: ['yours']}
    key: sunday
    new_key: 6
    inverse before: {8: ['beautiful'], 4: ['ours'], 5: ['yours']}
    inverse in the middle: {8: ['beautiful'], 4: ['ours'], 5: ['yours'], 6: []}
    value before: []
    value after: ['sunday']
    inverse after: {8: ['beautiful'], 4: ['ours'], 5: ['yours'], 6: ['sunday']}
    key: happy
    new_key: 5
    inverse before: {8: ['beautiful'], 4: ['ours'], 5: ['yours'], 6: ['sunday']}
    inverse in the middle: {8: ['beautiful'], 4: ['ours'], 5: ['yours'], 6: ['sunday']}
    value before: ['yours']
    value after: ['yours', 'happy']
    inverse after: {8: ['beautiful'], 4: ['ours'], 5: ['yours', 'happy'], 6: ['sunday']}
    key: fun
    new_key: 3
    inverse before: {8: ['beautiful'], 4: ['ours'], 5: ['yours', 'happy'], 6: ['sunday']}
    inverse in the middle: {8: ['beautiful'], 3: [], 4: ['ours'], 5: ['yours', 'happy'], 6: ['sunday']}
    value before: []
    value after: ['fun']
    inverse after: {8: ['beautiful'], 3: ['fun'], 4: ['ours'], 5: ['yours', 'happy'], 6: ['sunday']}
    key: mine
    new_key: 4
    inverse before: {8: ['beautiful'], 3: ['fun'], 4: ['ours'], 5: ['yours', 'happy'], 6: ['sunday']}
    inverse in the middle: {8: ['beautiful'], 3: ['fun'], 4: ['ours'], 5: ['yours', 'happy'], 6: ['sunday']}
    value before: ['ours']
    value after: ['ours', 'mine']
    inverse after: {8: ['beautiful'], 3: ['fun'], 4: ['ours', 'mine'], 5: ['yours', 'happy'], 6: ['sunday']}
    key: friend
    new_key: 6
    inverse before: {8: ['beautiful'], 3: ['fun'], 4: ['ours', 'mine'], 5: ['yours', 'happy'], 6: ['sunday']}
    inverse in the middle: {8: ['beautiful'], 3: ['fun'], 4: ['ours', 'mine'], 5: ['yours', 'happy'], 6: ['sunday']}
    value before: ['sunday']
    value after: ['sunday', 'friend']
    inverse after: {8: ['beautiful'], 3: ['fun'], 4: ['ours', 'mine'], 5: ['yours', 'happy'], 6: ['sunday', 'friend']}
    {8: ['beautiful'], 3: ['fun'], 4: ['ours', 'mine'], 5: ['yours', 'happy'], 6: ['sunday', 'friend']}
    

    Also very useful is a good debugger such as the one in PyCharm. Try that out.