Rocketman
Rocketman

Reputation: 3524

Chained Method Calls in Python with dict

I needed to flatten a dictionary today. Meaning I wanted:

{'_id': 0, 'sub': {'a': 1, 'b':2}}

to become:

{'_id': 0, 'a':1, 'b':2}

So I thought I could be clever and pull off the following one-liner.

One-liner:

x = dict(_id=0, sub=dict(a=1, b=2))
y = x.pop('sub').update(x)  # incorrect result

This results in y = None.

So I obviously resorted to:

Multi-Step:

x = dict(_id=0, sub=dict(a=1, b=2))
y = x.pop('sub')
y.update(x)   # correct result

Setting "good expressive coding practices" asside for a moment, I would like to understand why the One-liner approach above yields None. I would have thought that x.pop('sub') would have resulted in a temporary dict on a stack somewhere and the original x dict would be immediately updated. Then the stack object would receive the next method in the chain which is the update. This obviously does not seem to be the case.

For the communities better understanding (and clearly mine) - how does python resolve the one-liner and result in None?

Upvotes: 1

Views: 672

Answers (3)

Martijn Pieters
Martijn Pieters

Reputation: 1121834

The .update() method returns None, because it alters the affected dictionary in-place. .pop() does return the popped value, the nested dictionary.

You are updating the contained dict, why not update the parent dict instead?

x.update(x.pop('sub'))

Result:

>>> x = dict(_id=0, sub=dict(a=1, b=2))
>>> x.update(x.pop('sub'))
>>> x
{'a': 1, '_id': 0, 'b': 2}

Or you could do this recursively:

def inplace_flatten(d):
    keys = list(d.keys())
    for k in keys:
        if isinstance(d[k], dict):
            inplace_flatten(d[k])
            d.update(d.pop(k))

Upvotes: 10

Silas Ray
Silas Ray

Reputation: 26160

Because y gets the result of dict.update(), which is None.

Upvotes: 2

inspectorG4dget
inspectorG4dget

Reputation: 113945

This should do the trick

def flatten(d, answer=None):
    if answer == None:
        answer = {}
    if not d:
        return answer
    else:
        for k in d:
            if isinstance(d[k], dict):
                flatten(d[k], answer)
            else:
                answer[k] = d[k]
        return answer

Upvotes: 1

Related Questions