简体   繁体   中英

python collections.defaultdict on assignment but not on lookup

I have the following code:

from collections import *
nested_dict = lambda: defaultdict(nested_dict)
data = nested_dict()

which enables me to write any new "path" in the dict as a one liner:

data['A']['B']['C']=3

which is what I want. But I want to get an exception when running (for any non existing path):

var = data['A']['XXX']['C']

I feel I need defaultdict when writing, plain dict when reading...

Or, is there a simple nice way to check if a 'path' exists in a defaultdict without modifying its contents...

I tried converting the defaultdict back to a dict before the lookup, hoping that:

dict(data)['A']['XXX']['C']

would raise a exception... but it kept creating missing keys...

An obvious solution is to just use plain dicts with a function that can "materialize" the intermediate keys:

def write_path(d, path, value):
    for key in path[:-1]:
        d = d.setdefault(key, {})
    d[path[-1]] = value

d = {}

write_path(d, ['a', 'b', 'c'], 3)
print(d)
print(d['a']['b']['c'])
print(d['a']['b']['d'])

outputs

{'a': {'b': {'c': 3}}}
3
Traceback (most recent call last):
  File "writedefaultdict.py", line 11, in <module>
    print(d['a']['b']['d'])
KeyError: 'd'

You can't distingsuish between lookups and writes here, because it is the lookups that create your intermediary structure in the data['A']['B']['C'] = 3 assignment. Python executes the indexing operations data['A'] and then ['B'] first, before assigning to the 'C' key. The __getitem__ , __setitem__ and __missing__ hooks involved to make that work are not given enough context to distinguish between access that then leads to the 'C' assignment from only 'reading' 'XXX' in your second example.

You really only have 3 options here:

  • Don't use defaultdict . When writing, explicitly create new nested dictionaries with dict.setdefault() instead; you can chain these calls as needed:

     var = {} var.setdefault('A', {}).setdefault('B', {})['C'] = 3 

    or you can wrap recursive behaviour in a few functions .

  • Create a recursive copy of your defaultdict structure to replace it with a dict structure once you are done writing:

     def dd_to_d(dd): r = {} stack = [(r, dd)] while stack: target, dd = stack.pop() for key, value in dd.items(): if isinstance(value, defaultdict): sub = {} stack.append((sub, value)) value = sub target[key] = value return r var = dd_to_d(var) 
  • Set all the default_factory attributes to None to disable creating new values for missing keys:

     def disable_dd(dd): stack = [dd] while stack: dd = stack.pop() dd.default_factory = None for key, value in dd.items(): if isinstance(value, defaultdict): stack.append(value) disable_dd(var) 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM