Search code examples
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.