Get 'NoneType' object has no attribute 'extend' error message while trying to retrieve dictionary keys into list

Viewed 1584

I'm using Python 3.8.0 and I try to write a function which extracts all the keys of nested dictionaries and gathers the result in a list. Here is an example:

mydict = {"k1":"v1", "k2":"v2", "k3":{"k4":"v4","k5":{"k6":{"k7":"v7","k8":"v8"}}}}

What I expect as the output is:

[k1, k2, k3, k4, k5, k7, k8]

Here is a recursive function that I've defined :

def extract_dict_keys(d):
    if isinstance(d, dict):
        result = []
        for k, v in d.items():
            if isinstance(v, dict):
                result.append(k).extend(extract_dict_keys(v))
            else:
                result.append(k)
        return result
    else:
        [None]

But when I call extract_dict_keys(mydict) I get the following error message:

>>> extract_dict_keys(mydict)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in extract_dict_keys
AttributeError: 'NoneType' object has no attribute 'extend'
>>> 

Could you kindly indicate what I misunderstand? I don't see the problem.

4 Answers

result.append(k) returns None it only adds k to end of the list

to fix it you can split it to two lines

result.append(k)
result.extend(extract_dict_keys(v)) 

As the others answers have pointed out, append returns None, so you cannot do result.append(k).extend(extract_dict_keys(v)), since that extends a NoneType, not a list. You need to first add the key, then continue the recursion.

An easy way would be to add the key before entering the if to check if the value is of type dict:

mydict = {"k1":"v1", "k2":"v2", "k3":{"k4":"v4","k5":{"k6":{"k7":"v7","k8":"v8"}}}}

def extract_dict_keys(d):
    keys = []

    for k, v in d.items():
        keys.append(k)
        if isinstance(v, dict):
            keys.extend(extract_dict_keys(v))

    return keys

print(extract_dict_keys(mydict))
# ['k1', 'k2', 'k3', 'k4', 'k5', 'k6', 'k7', 'k8']

As noted, the error is from calling extend() on the result of append(), which is None.

It's often nicer to code this sort of recursion with a generator. This has several benefits: you don't need to maintain the inner state, you save memory, and the code is simpler:

mydict = {"k1":"v1", "k2":"v2", "k3":{"k4":"v4","k5":{"k6":{"k7":"v7","k8":"v8"}}}}

def extract_dict_keys(d):
    if not isinstance(d, dict):
        return
    for k, v in d.items():
        yield k
        yield from extract_dict_keys(v)

list(extract_dict_keys(mydict))
# ['k1', 'k2', 'k3', 'k4', 'k5', 'k6', 'k7', 'k8']

Append returns None so you need to separate the code like below:

def extract_dict_keys(d):
    if isinstance(d, dict):
        result = []
        for k, v in d.items():
            if isinstance(v, dict):
                result.append(k).
                result.extend(extract_dict_keys(v))
            else:
                result.append(k)
        return result
    else:
        [None]
Related