3

I have class with a nested dictionary data object. I need to get all the key values from it. What's the best efficient way to do this?

I'm stuck with following:

for k,v in data.items():
    print v.keys()

This is the data:

data = {
    "BANK": {
        "no_data": "INT",
    },
    "SHOCK": {
        "drop": "NOTI",
        "rise": "NOTI",
        "high_risk": "ALERT",
    },
    "OFFLINE": {"online": None, "offline_few": "ALERT"},
}
1
  • 4
    Please provide expected output. Commented Apr 9, 2020 at 12:04

5 Answers 5

3

An elegant way to concatenate lists (your value.keys() lists) into one is using a double-loop list comprehenstion, like this:

nested_keys = [
    key
    for val in data.values()
    for key in val.keys()]
Sign up to request clarification or add additional context in comments.

1 Comment

This would get keys of the values dictionaries only. This wouldn't get "BANK,"SHOCK","OFFLINE". OP's question is vague though.
1

Using a generator:

def all_keys(d):
    for k, v in d.items():
        yield k
        # assume that anything with `items` property will be a mapping
        # this can be replaced with: `isinstance(v, dict)` or `isinstance(v, collections.Mapping)`
        if hasattr(v, 'items'):  
            yield from all_keys(v)

On your input this produces:

data = {
    "BANK": {
        "no_data": "INT",
    },
    "SHOCK": {
        "drop": "NOTI",
        "rise": "NOTI",
        "high_risk": "ALERT",
    },
    "OFFLINE": {"online": None, "offline_few": "ALERT"},
}
print(list(all_keys(data)))
# ['BANK', 'no_data', 'SHOCK', 'drop', 'rise', 'high_risk', 'OFFLINE', 'online', 'offline_few']

Comments

0

If all your "actual" key-value pairs are at a certain depth, for example for depth 1, you can go like:

data = {
    "BANK": {
        "no_data": "INT",
    },
    "SHOCK": {
        "drop": "NOTI",
        "rise": "NOTI",
        "high_risk": "ALERT",
    },
    "OFFLINE": {"online": None, "offline_few": "ALERT"},
}
dic = {k:v for val in data.values() for k,v in val.items()}

But if you dont know that:

data = {
    "BANK": {
        "no_data": "INT",
    },
    "SHOCK": {
        "drop": "NOTI",
        "rise": "NOTI",
        "high_risk": "ALERT",
    },
    "online": None, 
    "offline_few": "ALERT"
}

In this case you need to use recursion:

def unnest(dic, final=dict()):
    for key, val in dic.items():
        if not isinstance(val, dict):
            final[key] = val
        else:
            dic2 = dict()
            for k, v in val.items():
                dic2[k] = v
            unnest(dic2, final)
    return final    

dic = unnest(data, {}) #every use of the function should have {} to solve issue pointed by @Ch3steR

In any case, once you have the "un-nested" dictionary it is trivial to print out the keys:

print(dic.keys())

3 Comments

Check your function it will encounter least astonishment and mutable default argument. To check if it does do unnest.__defaults__
@Ch3steR, thanks for sharing that. I didnt know such a thing existed in python. Updated my answer to solve the issue, I just had to make sure that each time the function is called it starts with a fresh empty dictionary.
@Ch3steR, here is a piece of code that shows then solves the issue: repl.it/repls/UselessWorseNewsaggregator
0

Recursive Function

Gets all keys at all levels of nested dictionary

def get_keys(d, result = None): 
  # use default of None to fix issue noted by @Ch3steR
  # namely: http://effbot.org/zone/default-values.htm
  if result is None:
    result = []

  for k, v in d.items():
    if isinstance(v, dict):
        result.append(k)
        get_keys(v, result)
    else:
      result.append(k)

  return result

Test

print(get_keys(data)) 

Output

['BANK', 'no_data', 'SHOCK', 'drop', 'rise', 'high_risk', 'OFFLINE', 'online', 'offline_few']

7 Comments

You can check it yourself run the function twice. After calling it once get_keys.__defaults__ will be (['BANK', 'no_data', 'SHOCK', 'drop', 'rise', 'high_risk', 'OFFLINE', 'online', 'offline_few'],) after second time get_keys.__defaults__ will be (['BANK', 'no_data', 'SHOCK', 'drop', 'rise', 'high_risk', 'OFFLINE', 'online', 'offline_few', 'BANK', 'no_data', 'SHOCK', 'drop', 'rise', 'high_risk', 'OFFLINE', 'online', 'offline_few'])
Here's link to check.
@Ch3steR, my solution will not have this issue, probably; could you check?
@PiyushSingh If you have a mutable default parameter don't mutate it in-place. Just run it twice or thrice and use func.__defaults__ will you get to know.
|
0

You could use a NestedDict. First install ndicts

pip install ndicts

Then

from ndicts.ndicts import NestedDict
data = {
    "BANK": {
        "no_data": "INT",
    },
    "SHOCK": {
        "drop": "NOTI",
        "rise": "NOTI",
        "high_risk": "ALERT",
    },
    "OFFLINE": {"online": None, "offline_few": "ALERT"},
}
nd = NestedDict(data)

The result

>>> list(nd.keys())
[('BANK', 'no_data'), ('SHOCK', 'drop'), ('SHOCK', 'rise'), ('SHOCK', 'high_risk'), ('OFFLINE', 'online'), ('OFFLINE', 'offline_few')]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.